├── .gitignore ├── LICENSE ├── README.md ├── build.sh ├── docs └── _config.yml ├── src ├── bitbotlib │ └── bitbot.proto ├── console │ └── main.go └── robot │ ├── bot.go │ ├── btccacccount.go │ ├── btccacconnt.go │ ├── btcchttp.go │ ├── common.go │ ├── configstore.go │ ├── currency.go │ ├── docker.go │ ├── exchange.go │ ├── exchangeaccount.go │ ├── huobiaccount.go │ ├── huobihttp.go │ ├── main.go │ ├── okcoinaccount.go │ ├── okcoinhttp.go │ ├── sample-config.json │ ├── strategy.go │ ├── test.go │ ├── utils.go │ └── utils_test.go └── strategy ├── first.js └── second.js /.gitignore: -------------------------------------------------------------------------------- 1 | src/robot/config.json 2 | .vscode/* 3 | bin/* 4 | dist/* 5 | pkg/* 6 | public/* 7 | ref/* 8 | src/bitbotlib/bitbot.pb.go 9 | src/robot/robot 10 | src/robot/console 11 | .DS_Store 12 | hugo/* 13 | blog/* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bitbot 2 | A cryptocurrency quantitative trading platform. 3 | BitBot平台专注于为数字货币的量化交易者提供服务。在这里你可以编写、回测、运行和分享量化交易策略。 4 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # argument "p" means Platform: 4 | # p=mac: GOOS=darwin GOARCH=amd64 5 | # p=win32: GOOS=windows GOARCH=386 6 | # p=win64: GOOS=windows GOARCH=amd64 7 | # p=linux: GOOS=linux GOARCH=amd64 8 | # p=arm: GOOS=linux GOARCH=arm 9 | 10 | GOOS="" 11 | GOARCH="" 12 | Platform="" 13 | Version=`git describe --tags` 14 | BuildLDFlags="-X main.version=$Version -X main.buildstamp=`date -u '+%Y-%m-%d_%I:%M:%S%p'` -X main.githash=`git rev-parse HEAD`" 15 | 16 | echo $BuildLDFlags 17 | 18 | while getopts "p:v:w" arg #选项后面的冒号表示该选项需要参数 19 | do 20 | case $arg in 21 | p) 22 | Platform=$OPTARG 23 | ;; 24 | ?) #当有不认识的选项的时候arg为? 25 | echo "Example usage: build.sh -p mac|win32|win64|linux|arc -v 0.1.0" 26 | ;; 27 | esac 28 | done 29 | 30 | if [[ -z $Platform ]]; then 31 | echo "Example usage: build.sh -p mac/win32/win64/linux/arc" 32 | else 33 | echo "Platform=$Platform" 34 | fi 35 | 36 | if [ -z $Platform ]; then 37 | GOOS="darwin" 38 | GOARCH="amd64" 39 | elif [ $Platform = "win32" ]; then 40 | GOOS="windows" 41 | GOARCH="386" 42 | elif [ $Platform = "win64" ];then 43 | GOOS="windows" 44 | GOARCH="amd64" 45 | elif [ $Platform = "linux" ];then 46 | GOOS="linux" 47 | GOARCH="amd64" 48 | elif [ $Platform = "arm" ];then 49 | GOOS="linux" 50 | GOARCH="arm" 51 | else 52 | GOOS="darwin" 53 | GOARCH="amd64" 54 | fi 55 | 56 | echo "GOOS=$GOOS" 57 | echo "GOARCH=$GOARCH" 58 | 59 | ROOT=$(pwd) 60 | echo $GOROOT 61 | echo $GOPATH 62 | echo $GOOS 63 | echo $GOARCH 64 | echo "############ Running go get. ############" 65 | #go get google.golang.org/grpc 66 | #go get -u github.com/golang/protobuf/{proto,protoc-gen-go} 67 | #go get github.com/robertkrimen/otto 68 | #go get github.com/markcheno/go-talib 69 | #Todo add more go get or use gb tool 70 | 71 | echo "############ Build protoc. ############" 72 | cd $ROOT/src/bitbotlib 73 | protoc --go_out=plugins=grpc:. bitbot.proto 74 | 75 | echo "############ build and install library. ############" 76 | 77 | cd $ROOT/src/bitbotlib 78 | GOOS=$GOOS GOARCH=$GOARCH go install 79 | 80 | echo "############ remove all binary files. ############" 81 | cd $ROOT/bin 82 | #rm -rf * 83 | 84 | echo "############ build and install console. ############" 85 | cd $ROOT/src/console 86 | GOOS=$GOOS GOARCH=$GOARCH go install -ldflags "$BuildLDFlags" 87 | 88 | echo "############ build and install robot. ############" 89 | cd $ROOT/src/robot 90 | GOOS=$GOOS GOARCH=$GOARCH go install -ldflags "$BuildLDFlags" 91 | 92 | echo "############ copy files to dist folder. ############" 93 | cd $ROOT/dist 94 | if [ -d "$Version" ]; then 95 | rm -rf $Version 96 | fi 97 | mkdir $Version 98 | cp -aR $ROOT/bin/. $ROOT/dist/$Version 99 | 100 | echo "############ upload files to dist folder. ############" 101 | 102 | cd $ROOT 103 | echo "############ Done. ############" -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-architect -------------------------------------------------------------------------------- /src/bitbotlib/bitbot.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package bitbotlib; 4 | 5 | enum FOO { X = 0; }; 6 | 7 | message Test { 8 | string label = 1; 9 | int32 type = 2; 10 | repeated int64 reps = 3; 11 | } -------------------------------------------------------------------------------- /src/console/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/robot/bot.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "net/smtp" 7 | "time" 8 | 9 | "fmt" 10 | "net" 11 | 12 | "os" 13 | "strings" 14 | 15 | "errors" 16 | 17 | "github.com/robertkrimen/otto" 18 | _ "github.com/robertkrimen/otto/underscore" 19 | ) 20 | 21 | //"github.com/d4l3k/talib" 22 | 23 | type BotStatus int 24 | 25 | const ( 26 | BOT_RUNNING BotStatus = iota 27 | BOT_STOPPED 28 | BOT_STARTING 29 | BOT_STOPPING 30 | ) 31 | 32 | type BotLogType int 33 | 34 | const ( 35 | ERROR_LOG BotLogType = iota 36 | PROFIT_LOG 37 | INFO_LOG 38 | BUY_LOG 39 | SELL_LOG 40 | CANCEL_LOG 41 | ) 42 | 43 | type BotLog struct { 44 | Stamp time.Time 45 | ExchangeName string 46 | Type BotLogType 47 | Price float64 48 | Amount float64 49 | Message string 50 | } 51 | 52 | type BotConfig struct { 53 | Name string 54 | StrategyID string 55 | DockerID string 56 | KLineInterval KLineIntervalType 57 | ExchangeAccountAPIIDs string 58 | Status BotStatus 59 | StatusDescription string 60 | Enabled bool 61 | Backtesting bool 62 | PaperTrading bool 63 | //StartTime time.Time 64 | //StopTime time.Time 65 | StrategyVarabileValues map[string]StrategyVarabileValue 66 | } 67 | 68 | type Bot struct { 69 | BotConfig 70 | 71 | jsVM *otto.Otto 72 | docker *Docker 73 | botLogs []BotLog 74 | commands string 75 | lastError string 76 | errorFilter string 77 | botStatusLog string 78 | autoLogEnable bool 79 | exchangeAccounts []IExchangeAccount 80 | } 81 | 82 | func (bot *Bot) Start() (err error) { 83 | var code string 84 | var exchangeAcct IExchangeAccount 85 | 86 | //getting strategy code 87 | strategy := docker.StrategieConfigs[bot.StrategyID] 88 | if strategy.Code != "" { 89 | code = strategy.Code 90 | } else { 91 | if retrieved, err := ioutil.ReadFile(strategy.LocalPath); err != nil { 92 | log.Print(err.Error()) 93 | return err 94 | } else { 95 | code = string(retrieved) 96 | } 97 | } 98 | 99 | //setup exchanges 100 | acctIDs := strings.Split(bot.ExchangeAccountAPIIDs, ",") 101 | for _, acctID := range acctIDs { 102 | acct := docker.ExchangeAccountConfigs[acctID] 103 | switch acct.ExchangeName { 104 | case "BTCC": 105 | exchangeAcct = new(BTCCExchangeAccount) 106 | case "HUOBI": 107 | exchangeAcct = new(HUOBIExchangeAccount) 108 | case "OKCOINCNY": 109 | exchangeAcct = new(OKCoinExchangeAccount) 110 | case "OKCOINUSD": 111 | exchangeAcct = new(OKCoinExchangeAccount) 112 | default: 113 | log.Printf("Unknown Exchange %s.", acct.ExchangeName) 114 | } 115 | if exchangeAcct == nil { 116 | return errors.New("Cannot Create ExchangeAccouant.") 117 | } else { 118 | exchangeAcct.Setup(docker.ExchangeAccountConfigs[acctID]) 119 | //exchange.Start() 120 | bot.exchangeAccounts = append(bot.exchangeAccounts, exchangeAcct) 121 | 122 | } 123 | } 124 | 125 | log.Printf("Starting Bot %s\n", bot.Name) 126 | bot.jsVM = otto.New() 127 | 128 | bot.setupJSVM() 129 | 130 | go func() { 131 | if _, err = bot.jsVM.Run(code); err != nil { 132 | log.Print(err.Error()) 133 | return 134 | } 135 | mainfunc, _ := bot.jsVM.Get("main") 136 | _, err = mainfunc.Call(otto.NullValue()) 137 | if err != nil { 138 | log.Print(err.Error()) 139 | return 140 | } 141 | }() 142 | 143 | return nil 144 | } 145 | func (bot *Bot) setupJSVM() { 146 | bot.jsVM.Set("Version", bot.docker.version) 147 | bot.jsVM.Set("exchanges", bot.exchangeAccounts) 148 | bot.jsVM.Set("exchange", bot.exchangeAccounts[0]) 149 | 150 | bot.jsVM.Set("Sleep", bot.Sleep) 151 | bot.jsVM.Set("LogProfit", bot.LogProfit) 152 | bot.jsVM.Set("IsBacktesting", bot.IsBacktesting) 153 | bot.jsVM.Set("IsPaperTrading", bot.IsPaperTrading) 154 | bot.jsVM.Set("Dial", bot.Dial) 155 | bot.jsVM.Set("HttpQuery", bot.HttpQuery) 156 | bot.jsVM.Set("Mail", bot.Mail) 157 | bot.jsVM.Set("GetCommand", bot.GetCommand) 158 | bot.jsVM.Set("GetPid", bot.GetPid) 159 | bot.jsVM.Set("Log", bot.Log) 160 | bot.jsVM.Set("GetLastError", bot.GetLastError) 161 | bot.jsVM.Set("SetErrorFilter", bot.SetErrorFilter) 162 | bot.jsVM.Set("EnableLog", bot.EnableLog) 163 | bot.jsVM.Set("LogStatus", bot.LogStatus) 164 | bot.jsVM.Set("LogReset", bot.LogReset) 165 | bot.jsVM.Set("LogProfitReset", bot.LogProfitReset) 166 | bot.jsVM.Set("LogProfit", bot.LogProfit) 167 | //bot.jsVM.Set("Sin", talib.Sin) 168 | 169 | } 170 | 171 | func (bot *Bot) Stop() { 172 | if bot.jsVM != nil { 173 | exitfunc, _ := bot.jsVM.Get("onExit") 174 | _, err := exitfunc.Call(otto.NullValue()) 175 | if err != nil { 176 | log.Print(err.Error()) 177 | } 178 | } else { 179 | log.Println("jsvm is nil") 180 | } 181 | } 182 | 183 | /*TODO 184 | Chart({...}) 185 | _G(K, V) 186 | //EnableWebsocket() 187 | */ 188 | 189 | // func (bot Bot) Remove() { 190 | 191 | // } 192 | 193 | // func (Bot Bot) OnConfigChanged() { 194 | 195 | // } 196 | 197 | func (bot *Bot) Sleep(ms int64) { 198 | time.Sleep(time.Duration(ms) * time.Millisecond) 199 | } 200 | 201 | //Log is exported to js VM. only log strings 202 | func (bot *Bot) Log(log string) { 203 | if verbose { 204 | fmt.Println("Get log from JS VM:" + log) 205 | } 206 | var botLog BotLog 207 | botLog.Message = log 208 | botLog.Type = INFO_LOG 209 | bot.botLogs = append(bot.botLogs, botLog) 210 | return 211 | } 212 | 213 | func (bot *Bot) LogProfit(profit float64) { 214 | var botLog BotLog 215 | botLog.Amount = profit 216 | botLog.Type = PROFIT_LOG 217 | bot.botLogs = append(bot.botLogs, botLog) 218 | return 219 | } 220 | 221 | func (bot *Bot) IsBacktesting() bool { 222 | return bot.Backtesting 223 | } 224 | 225 | func (bot *Bot) IsPaperTrading() bool { 226 | return bot.PaperTrading 227 | } 228 | 229 | //Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only), "udp", "udp4" (IPv4-only), 230 | //"udp6" (IPv6-only), "ip", "ip4" (IPv4-only), "ip6" (IPv6-only), "unix", "unixgram" and "unixpacket". 231 | //For TCP and UDP networks, addresses have the form host:port. 232 | //If host is a literal IPv6 address it must be enclosed in square brackets as in "[::1]:80" 233 | //or "[ipv6-host%zone]:80". The functions JoinHostPort and SplitHostPort manipulate addresses in this form. 234 | //If the host is empty, as in ":80", the local system is assumed. 235 | 236 | func (bot *Bot) Dial(network, address string, timeout time.Duration) net.Conn { 237 | conn, err := net.DialTimeout(network, address, timeout) 238 | if err != nil { 239 | fmt.Println(err.Error()) 240 | } 241 | //TODO 242 | return conn 243 | } 244 | 245 | func (bot *Bot) HttpQuery(url string, postData string, cookies string, headers string, isReturnHeader bool) string { 246 | //TODO 247 | // var client http.Client 248 | // var method string 249 | // if postData == "" || postData == nil { 250 | // method = "POST" 251 | // } else { 252 | // method = "GET" 253 | // } 254 | 255 | // if req, err := http.NewRequest(method, url, nil); err != nil { 256 | // log.Println(err.Error) 257 | // return "" 258 | // } 259 | 260 | return "" 261 | } 262 | 263 | func (bot *Bot) Mail(smtpServer string, smtpUsername string, smtpPassword string, mailTo string, title string, body string) bool { 264 | // Set up authentication information. 265 | auth := smtp.PlainAuth("", smtpUsername, smtpPassword, smtpServer) 266 | 267 | // Connect to the server, authenticate, set the sender and recipient, 268 | // and send the email all in one step. 269 | to := strings.Split(mailTo, ";") 270 | 271 | msg := []byte("To:" + to[0] + "\r\n" + 272 | "Subject: " + title + "\r\n" + 273 | body + " \r\n") 274 | err := smtp.SendMail(smtpServer, auth, "noreply@bitbot.com.cn", to, msg) 275 | if err != nil { 276 | log.Fatal(err) 277 | return false 278 | } 279 | return true 280 | } 281 | 282 | func (bot *Bot) GetCommand() string { 283 | cmd := bot.commands 284 | bot.commands = "" 285 | return cmd 286 | } 287 | 288 | func (bot *Bot) GetPid() int { 289 | return os.Getpid() 290 | } 291 | 292 | func (bot *Bot) GetLastError() string { 293 | return bot.lastError 294 | } 295 | 296 | func (bot *Bot) SetErrorFilter(filter string) { 297 | bot.errorFilter = filter 298 | return 299 | } 300 | 301 | func (bot *Bot) EnableLog(enable bool) { 302 | bot.autoLogEnable = enable 303 | return 304 | } 305 | 306 | func (bot *Bot) LogStatus(status string) { 307 | bot.botStatusLog = status 308 | return 309 | } 310 | 311 | func (bot *Bot) LogReset(recordsToLeave interface{}) { 312 | 313 | if records, ok := recordsToLeave.(int); ok { 314 | if records < len(bot.botLogs) { 315 | bot.botLogs = bot.botLogs[len(bot.botLogs)-records:] 316 | } 317 | } else { 318 | bot.botLogs = bot.botLogs[0:0] 319 | } 320 | return 321 | } 322 | 323 | func (bot *Bot) LogProfitReset(recordsToLeave int) { 324 | return 325 | } 326 | 327 | // func runUnsafe(unsafe string) { 328 | // start := time.Now() 329 | // defer func() { 330 | // duration := time.Since(start) 331 | // if caught := recover(); caught != nil { 332 | // if caught == halt { 333 | // fmt.Fprintf(os.Stderr, "Some code took to long! Stopping after: %v\n", duration) 334 | // return 335 | // } 336 | // panic(caught) // Something else happened, repanic! 337 | // } 338 | // fmt.Fprintf(os.Stderr, "Ran code successfully: %v\n", duration) 339 | // }() 340 | 341 | // vm := otto.New() 342 | // vm.Interrupt = make(chan func(), 1) // The buffer prevents blocking 343 | 344 | // go func() { 345 | // time.Sleep(2 * time.Second) // Stop after two seconds 346 | // vm.Interrupt <- func() { 347 | // panic(halt) 348 | // } 349 | // }() 350 | 351 | // vm.Run(unsafe) // Here be dragons (risky code) 352 | // } 353 | -------------------------------------------------------------------------------- /src/robot/btccacccount.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type BTCCExchangeAccount struct { 4 | ExchangeAccountConfig 5 | btcc *BTCCExchange 6 | } 7 | 8 | func (b *BTCCExchangeAccount) Setup(acctConfig ExchangeAccountConfig) { 9 | b.ExchangeAccountConfig = acctConfig 10 | b.btcc = new(BTCCExchange) 11 | } 12 | func (b *BTCCExchangeAccount) GetName() string { 13 | return b.ExchangeName 14 | } 15 | 16 | func (b *BTCCExchangeAccount) GetLabel() string { 17 | return b.Label 18 | } 19 | 20 | func (b *BTCCExchangeAccount) GetAccount() (ExchangeAccountInfo, error) { 21 | return ExchangeAccountInfo{}, nil 22 | } 23 | 24 | func (b *BTCCExchangeAccount) GetEnabledPair() string { 25 | return b.EnabledPair 26 | } 27 | 28 | func (b *BTCCExchangeAccount) Start() { 29 | 30 | } 31 | 32 | func (b *BTCCExchangeAccount) SetDefaults() { 33 | 34 | } 35 | 36 | func (b *BTCCExchangeAccount) GetTicker() Ticker { 37 | btccTicker := b.btcc.GetTicker("BTCCNY") 38 | ticker := Ticker{} 39 | 40 | ticker.Buy = btccTicker.Buy 41 | ticker.High = btccTicker.High 42 | ticker.Last = btccTicker.Last 43 | ticker.Low = btccTicker.Low 44 | ticker.Sell = btccTicker.Sell 45 | ticker.Volume = btccTicker.Vol 46 | 47 | return ticker 48 | } 49 | -------------------------------------------------------------------------------- /src/robot/btccacconnt.go: -------------------------------------------------------------------------------- 1 | package main 2 | -------------------------------------------------------------------------------- /src/robot/btcchttp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "net/url" 8 | "strconv" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | const ( 14 | BTCC_API_URL = "https://api.btcc.com/" 15 | BTCC_API_AUTHENTICATED_METHOD = "api_trade_v1.php" 16 | BTCC_API_VER = "2.0.1.4" 17 | BTCC_ORDER_BUY = "buyOrder2" 18 | BTCC_ORDER_SELL = "sellOrder2" 19 | BTCC_ORDER_CANCEL = "cancelOrder" 20 | BTCC_ICEBERG_BUY = "buyIcebergOrder" 21 | BTCC_ICEBERG_SELL = "sellIcebergOrder" 22 | BTCC_ICEBERG_ORDER = "getIcebergOrder" 23 | BTCC_ICEBERG_ORDERS = "getIcebergOrders" 24 | BTCC_ICEBERG_CANCEL = "cancelIcebergOrder" 25 | BTCC_ACCOUNT_INFO = "getAccountInfo" 26 | BTCC_DEPOSITS = "getDeposits" 27 | BTCC_MARKETDEPTH = "getMarketDepth2" 28 | BTCC_ORDER = "getOrder" 29 | BTCC_ORDERS = "getOrders" 30 | BTCC_TRANSACTIONS = "getTransactions" 31 | BTCC_WITHDRAWAL = "getWithdrawal" 32 | BTCC_WITHDRAWALS = "getWithdrawals" 33 | BTCC_WITHDRAWAL_REQUEST = "requestWithdrawal" 34 | BTCC_STOPORDER_BUY = "buyStopOrder" 35 | BTCC_STOPORDER_SELL = "sellStopOrder" 36 | BTCC_STOPORDER_CANCEL = "cancelStopOrder" 37 | BTCC_STOPORDER = "getStopOrder" 38 | BTCC_STOPORDERS = "getStopOrders" 39 | ) 40 | 41 | type BTCCExchange struct { 42 | Name string 43 | Enabled bool 44 | Verbose bool 45 | Websocket bool 46 | RESTPollingDelay time.Duration 47 | AuthenticatedAPISupport bool 48 | APISecret, APIKey string 49 | Fee float64 50 | BaseCurrencies []string 51 | AvailablePairs []string 52 | EnabledPairs []string 53 | } 54 | 55 | type BTCCTicker struct { 56 | High float64 `json:",string"` 57 | Low float64 `json:",string"` 58 | Buy float64 `json:",string"` 59 | Sell float64 `json:",string"` 60 | Last float64 `json:",string"` 61 | Vol float64 `json:",string"` 62 | Date int64 63 | Vwap float64 `json:",string"` 64 | Prev_close float64 `json:",string"` 65 | Open float64 `json:",string"` 66 | } 67 | 68 | type BTCCProfile struct { 69 | Username string 70 | TradePasswordEnabled bool `json:"trade_password_enabled,bool"` 71 | OTPEnabled bool `json:"otp_enabled,bool"` 72 | TradeFee float64 `json:"trade_fee"` 73 | TradeFeeCNYLTC float64 `json:"trade_fee_cnyltc"` 74 | TradeFeeBTCLTC float64 `json:"trade_fee_btcltc"` 75 | DailyBTCLimit float64 `json:"daily_btc_limit"` 76 | DailyLTCLimit float64 `json:"daily_ltc_limit"` 77 | BTCDespoitAddress string `json:"btc_despoit_address"` 78 | BTCWithdrawalAddress string `json:"btc_withdrawal_address"` 79 | LTCDepositAddress string `json:"ltc_deposit_address"` 80 | LTCWithdrawalAddress string `json:"ltc_withdrawal_request"` 81 | APIKeyPermission int64 `json:"api_key_permission"` 82 | } 83 | 84 | type BTCCCurrencyGeneric struct { 85 | Currency string 86 | Symbol string 87 | Amount string 88 | AmountInt int64 `json:"amount_integer"` 89 | AmountDecimal float64 `json:"amount_decimal"` 90 | } 91 | 92 | type BTCCOrder struct { 93 | ID int64 94 | Type string 95 | Price float64 96 | Currency string 97 | Amount float64 98 | AmountOrig float64 `json:"amount_original"` 99 | Date int64 100 | Status string 101 | Detail BTCCOrderDetail 102 | } 103 | 104 | type BTCCOrderDetail struct { 105 | Dateline int64 106 | Price float64 107 | Amount float64 108 | } 109 | 110 | type BTCCWithdrawal struct { 111 | ID int64 112 | Address string 113 | Currency string 114 | Amount float64 115 | Date int64 116 | Transaction string 117 | Status string 118 | } 119 | 120 | type BTCCDeposit struct { 121 | ID int64 122 | Address string 123 | Currency string 124 | Amount float64 125 | Date int64 126 | Status string 127 | } 128 | 129 | type BTCCBidAsk struct { 130 | Price float64 131 | Amount float64 132 | } 133 | 134 | type BTCCDepth struct { 135 | Bid []BTCCBidAsk 136 | Ask []BTCCBidAsk 137 | } 138 | 139 | type BTCCTransaction struct { 140 | ID int64 141 | Type string 142 | BTCAmount float64 `json:"btc_amount"` 143 | LTCAmount float64 `json:"ltc_amount"` 144 | CNYAmount float64 `json:"cny_amount"` 145 | Date int64 146 | } 147 | 148 | type BTCCIcebergOrder struct { 149 | ID int64 150 | Type string 151 | Price float64 152 | Market string 153 | Amount float64 154 | AmountOrig float64 `json:"amount_original"` 155 | DisclosedAmount float64 `json:"disclosed_amount"` 156 | Variance float64 157 | Date int64 158 | Status string 159 | } 160 | 161 | type BTCCStopOrder struct { 162 | ID int64 163 | Type string 164 | StopPrice float64 `json:"stop_price"` 165 | TrailingAmt float64 `json:"trailing_amount"` 166 | TrailingPct float64 `json:"trailing_percentage"` 167 | Price float64 168 | Market string 169 | Amount float64 170 | Date int64 171 | Status string 172 | OrderID int64 `json:"order_id"` 173 | } 174 | 175 | //Setup is run on startup to setup exchange with config values 176 | func (b *BTCCExchange) Setup(exch ExchangeConfig) { 177 | b.RESTPollingDelay = exch.RESTPollingDelay 178 | b.BaseCurrencies = SplitStrings(exch.BaseCurrencies, ",") 179 | b.AvailablePairs = SplitStrings(exch.AvailablePairs, ",") 180 | } 181 | 182 | func (b *BTCCExchange) GetAvailablePairs() []string { 183 | return b.AvailablePairs 184 | } 185 | 186 | //Start is run if exchange is enabled, after Setup 187 | func (b *BTCCExchange) Start() { 188 | //go b.Run() 189 | } 190 | 191 | func (b *BTCCExchange) GetName() string { 192 | return b.Name 193 | } 194 | 195 | // func (b *BTCC) Run() { 196 | // if verbose { 197 | // log.Printf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay) 198 | // log.Printf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs) 199 | // } 200 | 201 | // for b.Enabled { 202 | // for _, x := range b.EnabledPairs { 203 | // currency := StringToLower(x) 204 | // go func() { 205 | // ticker := b.GetTicker(currency) 206 | // if currency != "ltcbtc" { 207 | // tickerLastUSD, _ := ConvertCurrency(ticker.Last, "CNY", "USD") 208 | // tickerHighUSD, _ := ConvertCurrency(ticker.High, "CNY", "USD") 209 | // tickerLowUSD, _ := ConvertCurrency(ticker.Low, "CNY", "USD") 210 | // log.Printf("BTCC %s: Last %f (%f) High %f (%f) Low %f (%f) Volume %f\n", currency, tickerLastUSD, ticker.Last, tickerHighUSD, ticker.High, tickerLowUSD, ticker.Low, ticker.Vol) 211 | // //AddExchangeInfo(b.GetName(), StringToUpper(currency[0:3]), StringToUpper(currency[3:]), ticker.Last, ticker.Vol) 212 | // //AddExchangeInfo(b.GetName(), StringToUpper(currency[0:3]), "USD", tickerLastUSD, ticker.Vol) 213 | // } else { 214 | // log.Printf("BTCC %s: Last %f High %f Low %f Volume %f\n", currency, ticker.Last, ticker.High, ticker.Low, ticker.Vol) 215 | // //AddExchangeInfo(b.GetName(), StringToUpper(currency[0:3]), StringToUpper(currency[3:]), ticker.Last, ticker.Vol) 216 | // } 217 | // }() 218 | // } 219 | // time.Sleep(time.Second * b.RESTPollingDelay) 220 | // } 221 | // } 222 | 223 | func (b *BTCCExchange) GetTicker(symbol string) BTCCTicker { 224 | type Response struct { 225 | Ticker BTCCTicker 226 | } 227 | 228 | resp := Response{} 229 | req := fmt.Sprintf("%sdata/ticker?market=%s", BTCC_API_URL, symbol) 230 | err := SendHTTPGetRequest(req, true, &resp) 231 | if err != nil { 232 | log.Println(err) 233 | return BTCCTicker{} 234 | } 235 | return resp.Ticker 236 | } 237 | 238 | // func (b *BTCC) GetTickerPrice(currency string) TickerPrice { 239 | // var tickerPrice TickerPrice 240 | // ticker := b.GetTicker(currency) 241 | // tickerPrice.Ask = ticker.Sell 242 | // tickerPrice.Bid = ticker.Buy 243 | // tickerPrice.CryptoCurrency = currency 244 | // tickerPrice.Low = ticker.Low 245 | // tickerPrice.Last = ticker.Last 246 | // tickerPrice.Volume = ticker.Vol 247 | // tickerPrice.High = ticker.High 248 | 249 | // return tickerPrice 250 | // } 251 | 252 | func (b *BTCCExchange) GetTradesLast24h(symbol string) bool { 253 | req := fmt.Sprintf("%sdata/trades?market=%s", BTCC_API_URL, symbol) 254 | err := SendHTTPGetRequest(req, true, nil) 255 | if err != nil { 256 | log.Println(err) 257 | return false 258 | } 259 | return true 260 | } 261 | 262 | func (b *BTCCExchange) GetTradeHistory(symbol string, limit, sinceTid int64, time time.Time) bool { 263 | req := fmt.Sprintf("%sdata/historydata?market=%s", BTCC_API_URL, symbol) 264 | v := url.Values{} 265 | 266 | if limit > 0 { 267 | v.Set("limit", strconv.FormatInt(limit, 10)) 268 | } 269 | if sinceTid > 0 { 270 | v.Set("since", strconv.FormatInt(sinceTid, 10)) 271 | } 272 | if !time.IsZero() { 273 | v.Set("sincetype", strconv.FormatInt(time.Unix(), 10)) 274 | } 275 | 276 | req = EncodeURLValues(req, v) 277 | err := SendHTTPGetRequest(req, true, nil) 278 | if err != nil { 279 | log.Println(err) 280 | return false 281 | } 282 | return true 283 | } 284 | 285 | func (b *BTCCExchange) GetOrderBook(symbol string, limit int) bool { 286 | req := fmt.Sprintf("%sdata/orderbook?market=%s&limit=%d", BTCC_API_URL, symbol, limit) 287 | err := SendHTTPGetRequest(req, true, nil) 288 | if err != nil { 289 | log.Println(err) 290 | return false 291 | } 292 | return true 293 | } 294 | 295 | func (b *BTCCExchange) GetAccountInfo(infoType string) { 296 | params := make([]interface{}, 0) 297 | 298 | if len(infoType) > 0 { 299 | params = append(params, infoType) 300 | } 301 | 302 | err := b.SendAuthenticatedHTTPRequest(BTCC_ACCOUNT_INFO, params) 303 | 304 | if err != nil { 305 | log.Println(err) 306 | } 307 | } 308 | 309 | func (b *BTCCExchange) PlaceOrder(buyOrder bool, price, amount float64, market string) { 310 | params := make([]interface{}, 0) 311 | params = append(params, strconv.FormatFloat(price, 'f', -1, 64)) 312 | params = append(params, strconv.FormatFloat(amount, 'f', -1, 64)) 313 | 314 | if len(market) > 0 { 315 | params = append(params, market) 316 | } 317 | 318 | req := BTCC_ORDER_BUY 319 | if !buyOrder { 320 | req = BTCC_ORDER_SELL 321 | } 322 | 323 | err := b.SendAuthenticatedHTTPRequest(req, params) 324 | 325 | if err != nil { 326 | log.Println(err) 327 | } 328 | } 329 | 330 | func (b *BTCCExchange) CancelOrder(orderID int64, market string) { 331 | params := make([]interface{}, 0) 332 | params = append(params, orderID) 333 | 334 | if len(market) > 0 { 335 | params = append(params, market) 336 | } 337 | 338 | err := b.SendAuthenticatedHTTPRequest(BTCC_ORDER_CANCEL, params) 339 | 340 | if err != nil { 341 | log.Println(err) 342 | } 343 | } 344 | 345 | func (b *BTCCExchange) GetDeposits(currency string, pending bool) { 346 | params := make([]interface{}, 0) 347 | params = append(params, currency) 348 | 349 | if pending { 350 | params = append(params, pending) 351 | } 352 | 353 | err := b.SendAuthenticatedHTTPRequest(BTCC_DEPOSITS, params) 354 | 355 | if err != nil { 356 | log.Println(err) 357 | } 358 | } 359 | 360 | func (b *BTCCExchange) GetMarketDepth(market string, limit int64) { 361 | params := make([]interface{}, 0) 362 | 363 | if limit > 0 { 364 | params = append(params, limit) 365 | } 366 | 367 | if len(market) > 0 { 368 | params = append(params, market) 369 | } 370 | 371 | err := b.SendAuthenticatedHTTPRequest(BTCC_MARKETDEPTH, params) 372 | 373 | if err != nil { 374 | log.Println(err) 375 | } 376 | } 377 | 378 | func (b *BTCCExchange) GetOrder(orderID int64, market string, detailed bool) { 379 | params := make([]interface{}, 0) 380 | params = append(params, orderID) 381 | 382 | if len(market) > 0 { 383 | params = append(params, market) 384 | } 385 | 386 | if detailed { 387 | params = append(params, detailed) 388 | } 389 | 390 | err := b.SendAuthenticatedHTTPRequest(BTCC_ORDER, params) 391 | 392 | if err != nil { 393 | log.Println(err) 394 | } 395 | } 396 | 397 | func (b *BTCCExchange) GetOrders(openonly bool, market string, limit, offset, since int64, detailed bool) { 398 | params := make([]interface{}, 0) 399 | 400 | if openonly { 401 | params = append(params, openonly) 402 | } 403 | 404 | if len(market) > 0 { 405 | params = append(params, market) 406 | } 407 | 408 | if limit > 0 { 409 | params = append(params, limit) 410 | } 411 | 412 | if offset > 0 { 413 | params = append(params, offset) 414 | } 415 | 416 | if since > 0 { 417 | params = append(params, since) 418 | } 419 | 420 | if detailed { 421 | params = append(params, detailed) 422 | } 423 | 424 | err := b.SendAuthenticatedHTTPRequest(BTCC_ORDERS, params) 425 | 426 | if err != nil { 427 | log.Println(err) 428 | } 429 | } 430 | 431 | func (b *BTCCExchange) GetTransactions(transType string, limit, offset, since int64, sinceType string) { 432 | params := make([]interface{}, 0) 433 | 434 | if len(transType) > 0 { 435 | params = append(params, transType) 436 | } 437 | 438 | if limit > 0 { 439 | params = append(params, limit) 440 | } 441 | 442 | if offset > 0 { 443 | params = append(params, offset) 444 | } 445 | 446 | if since > 0 { 447 | params = append(params, since) 448 | } 449 | 450 | if len(sinceType) > 0 { 451 | params = append(params, sinceType) 452 | } 453 | 454 | err := b.SendAuthenticatedHTTPRequest(BTCC_TRANSACTIONS, params) 455 | 456 | if err != nil { 457 | log.Println(err) 458 | } 459 | } 460 | 461 | func (b *BTCCExchange) GetWithdrawal(withdrawalID int64, currency string) { 462 | params := make([]interface{}, 0) 463 | params = append(params, withdrawalID) 464 | 465 | if len(currency) > 0 { 466 | params = append(params, currency) 467 | } 468 | 469 | err := b.SendAuthenticatedHTTPRequest(BTCC_WITHDRAWAL, params) 470 | 471 | if err != nil { 472 | log.Println(err) 473 | } 474 | } 475 | 476 | func (b *BTCCExchange) GetWithdrawals(currency string, pending bool) { 477 | params := make([]interface{}, 0) 478 | params = append(params, currency) 479 | 480 | if pending { 481 | params = append(params, pending) 482 | } 483 | 484 | err := b.SendAuthenticatedHTTPRequest(BTCC_WITHDRAWALS, params) 485 | 486 | if err != nil { 487 | log.Println(err) 488 | } 489 | } 490 | 491 | func (b *BTCCExchange) RequestWithdrawal(currency string, amount float64) { 492 | params := make([]interface{}, 0) 493 | params = append(params, currency) 494 | params = append(params, amount) 495 | 496 | err := b.SendAuthenticatedHTTPRequest(BTCC_WITHDRAWAL_REQUEST, params) 497 | 498 | if err != nil { 499 | log.Println(err) 500 | } 501 | } 502 | 503 | func (b *BTCCExchange) IcebergOrder(buyOrder bool, price, amount, discAmount, variance float64, market string) { 504 | params := make([]interface{}, 0) 505 | params = append(params, strconv.FormatFloat(price, 'f', -1, 64)) 506 | params = append(params, strconv.FormatFloat(amount, 'f', -1, 64)) 507 | params = append(params, strconv.FormatFloat(discAmount, 'f', -1, 64)) 508 | params = append(params, strconv.FormatFloat(variance, 'f', -1, 64)) 509 | 510 | if len(market) > 0 { 511 | params = append(params, market) 512 | } 513 | 514 | req := BTCC_ICEBERG_BUY 515 | if !buyOrder { 516 | req = BTCC_ICEBERG_SELL 517 | } 518 | 519 | err := b.SendAuthenticatedHTTPRequest(req, params) 520 | 521 | if err != nil { 522 | log.Println(err) 523 | } 524 | } 525 | 526 | func (b *BTCCExchange) GetIcebergOrder(orderID int64, market string) { 527 | params := make([]interface{}, 0) 528 | params = append(params, orderID) 529 | 530 | if len(market) > 0 { 531 | params = append(params, market) 532 | } 533 | 534 | err := b.SendAuthenticatedHTTPRequest(BTCC_ICEBERG_ORDER, params) 535 | 536 | if err != nil { 537 | log.Println(err) 538 | } 539 | } 540 | 541 | func (b *BTCCExchange) GetIcebergOrders(limit, offset int64, market string) { 542 | params := make([]interface{}, 0) 543 | 544 | if limit > 0 { 545 | params = append(params, limit) 546 | } 547 | 548 | if offset > 0 { 549 | params = append(params, offset) 550 | } 551 | 552 | if len(market) > 0 { 553 | params = append(params, market) 554 | } 555 | 556 | err := b.SendAuthenticatedHTTPRequest(BTCC_ICEBERG_ORDERS, params) 557 | 558 | if err != nil { 559 | log.Println(err) 560 | } 561 | } 562 | 563 | func (b *BTCCExchange) CancelIcebergOrder(orderID int64, market string) { 564 | params := make([]interface{}, 0) 565 | params = append(params, orderID) 566 | 567 | if len(market) > 0 { 568 | params = append(params, market) 569 | } 570 | 571 | err := b.SendAuthenticatedHTTPRequest(BTCC_ICEBERG_CANCEL, params) 572 | 573 | if err != nil { 574 | log.Println(err) 575 | } 576 | } 577 | 578 | func (b *BTCCExchange) PlaceStopOrder(buyOder bool, stopPrice, price, amount, trailingAmt, trailingPct float64, market string) { 579 | params := make([]interface{}, 0) 580 | 581 | if stopPrice > 0 { 582 | params = append(params, stopPrice) 583 | } 584 | 585 | params = append(params, strconv.FormatFloat(price, 'f', -1, 64)) 586 | params = append(params, strconv.FormatFloat(amount, 'f', -1, 64)) 587 | 588 | if trailingAmt > 0 { 589 | params = append(params, strconv.FormatFloat(trailingAmt, 'f', -1, 64)) 590 | } 591 | 592 | if trailingPct > 0 { 593 | params = append(params, strconv.FormatFloat(trailingPct, 'f', -1, 64)) 594 | } 595 | 596 | if len(market) > 0 { 597 | params = append(params, market) 598 | } 599 | 600 | req := BTCC_STOPORDER_BUY 601 | if !buyOder { 602 | req = BTCC_STOPORDER_SELL 603 | } 604 | 605 | err := b.SendAuthenticatedHTTPRequest(req, params) 606 | 607 | if err != nil { 608 | log.Println(err) 609 | } 610 | } 611 | 612 | func (b *BTCCExchange) GetStopOrder(orderID int64, market string) { 613 | params := make([]interface{}, 0) 614 | params = append(params, orderID) 615 | 616 | if len(market) > 0 { 617 | params = append(params, market) 618 | } 619 | 620 | err := b.SendAuthenticatedHTTPRequest(BTCC_STOPORDER, params) 621 | 622 | if err != nil { 623 | log.Println(err) 624 | } 625 | } 626 | 627 | func (b *BTCCExchange) GetStopOrders(status, orderType string, stopPrice float64, limit, offset int64, market string) { 628 | params := make([]interface{}, 0) 629 | 630 | if len(status) > 0 { 631 | params = append(params, status) 632 | } 633 | 634 | if len(orderType) > 0 { 635 | params = append(params, orderType) 636 | } 637 | 638 | if stopPrice > 0 { 639 | params = append(params, stopPrice) 640 | } 641 | 642 | if limit > 0 { 643 | params = append(params, limit) 644 | } 645 | 646 | if offset > 0 { 647 | params = append(params, limit) 648 | } 649 | 650 | if len(market) > 0 { 651 | params = append(params, market) 652 | } 653 | 654 | err := b.SendAuthenticatedHTTPRequest(BTCC_STOPORDERS, params) 655 | 656 | if err != nil { 657 | log.Println(err) 658 | } 659 | } 660 | 661 | func (b *BTCCExchange) CancelStopOrder(orderID int64, market string) { 662 | params := make([]interface{}, 0) 663 | params = append(params, orderID) 664 | 665 | if len(market) > 0 { 666 | params = append(params, market) 667 | } 668 | 669 | err := b.SendAuthenticatedHTTPRequest(BTCC_STOPORDER_CANCEL, params) 670 | 671 | if err != nil { 672 | log.Println(err) 673 | } 674 | } 675 | 676 | func (b *BTCCExchange) SendAuthenticatedHTTPRequest(method string, params []interface{}) (err error) { 677 | nonce := strconv.FormatInt(time.Now().UnixNano(), 10)[0:16] 678 | encoded := fmt.Sprintf("tonce=%s&accesskey=%s&requestmethod=post&id=%d&method=%s¶ms=", nonce, b.APIKey, 1, method) 679 | 680 | if len(params) == 0 { 681 | params = make([]interface{}, 0) 682 | } else { 683 | items := make([]string, 0) 684 | for _, x := range params { 685 | xType := fmt.Sprintf("%T", x) 686 | switch xType { 687 | case "int64", "int": 688 | { 689 | items = append(items, fmt.Sprintf("%d", x)) 690 | } 691 | case "string": 692 | { 693 | items = append(items, fmt.Sprintf("%s", x)) 694 | } 695 | case "float64": 696 | { 697 | items = append(items, fmt.Sprintf("%f", x)) 698 | } 699 | case "bool": 700 | { 701 | if x == true { 702 | items = append(items, "1") 703 | } else { 704 | items = append(items, "") 705 | } 706 | } 707 | default: 708 | { 709 | items = append(items, fmt.Sprintf("%v", x)) 710 | } 711 | } 712 | } 713 | encoded += JoinStrings(items, ",") 714 | } 715 | if verbose { 716 | log.Println(encoded) 717 | } 718 | 719 | hmac := GetHMAC(HASH_SHA1, []byte(encoded), []byte(b.APISecret)) 720 | postData := make(map[string]interface{}) 721 | postData["method"] = method 722 | postData["params"] = params 723 | postData["id"] = 1 724 | apiURL := BTCC_API_URL + BTCC_API_AUTHENTICATED_METHOD 725 | data, err := JSONEncode(postData) 726 | 727 | if err != nil { 728 | return errors.New("Unable to JSON Marshal POST data") 729 | } 730 | 731 | if verbose { 732 | log.Printf("Sending POST request to %s calling method %s with params %s\n", apiURL, method, data) 733 | } 734 | 735 | headers := make(map[string]string) 736 | headers["Content-type"] = "application/json-rpc" 737 | headers["Authorization"] = "Basic " + Base64Encode([]byte(b.APIKey+":"+HexEncodeToString(hmac))) 738 | headers["Json-Rpc-Tonce"] = nonce 739 | 740 | resp, err := SendHTTPRequest("POST", apiURL, headers, strings.NewReader(string(data))) 741 | 742 | if err != nil { 743 | return err 744 | } 745 | 746 | if verbose { 747 | log.Printf("Recv'd :%s\n", resp) 748 | } 749 | 750 | return nil 751 | } 752 | -------------------------------------------------------------------------------- /src/robot/common.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // type TickerPrice struct { 4 | // CryptoCurrency string `json:"CryptoCurrency"` 5 | // FiatCurrency string `json:"FiatCurrency"` 6 | // Last float64 `json:"Last"` 7 | // High float64 `json:"High"` 8 | // Low float64 `json:"Low"` 9 | // Bid float64 `json:"Bid"` 10 | // Ask float64 `json:"Ask"` 11 | // Volume float64 `json:"Volume"` 12 | // } 13 | 14 | // type OldTicker struct { 15 | // Price map[string]map[string]TickerPrice 16 | // ExchangeName string 17 | // } 18 | 19 | // func (t *Ticker) PriceToString(cryptoCurrency, fiatCurrency, priceType string) string { 20 | // switch priceType { 21 | // case "last": 22 | // return strconv.FormatFloat(t.Price[cryptoCurrency][fiatCurrency].Last, 'f', -1, 64) 23 | // case "high": 24 | // return strconv.FormatFloat(t.Price[cryptoCurrency][fiatCurrency].High, 'f', -1, 64) 25 | // case "low": 26 | // return strconv.FormatFloat(t.Price[cryptoCurrency][fiatCurrency].Low, 'f', -1, 64) 27 | // case "bid": 28 | // return strconv.FormatFloat(t.Price[cryptoCurrency][fiatCurrency].Bid, 'f', -1, 64) 29 | // case "ask": 30 | // return strconv.FormatFloat(t.Price[cryptoCurrency][fiatCurrency].Ask, 'f', -1, 64) 31 | // case "volume": 32 | // return strconv.FormatFloat(t.Price[cryptoCurrency][fiatCurrency].Volume, 'f', -1, 64) 33 | // default: 34 | // return "" 35 | // } 36 | // } 37 | 38 | // func AddTickerPrice(m map[string]map[string]TickerPrice, cryptocurrency, fiatcurrency string, price TickerPrice) { 39 | // mm, ok := m[cryptocurrency] 40 | // if !ok { 41 | // mm = make(map[string]TickerPrice) 42 | // m[cryptocurrency] = mm 43 | // } 44 | // mm[fiatcurrency] = price 45 | // } 46 | 47 | // func NewTicker(exchangeName string, prices []TickerPrice) *Ticker { 48 | // ticker := &Ticker{} 49 | // ticker.ExchangeName = exchangeName 50 | // ticker.Price = make(map[string]map[string]TickerPrice, 0) 51 | 52 | // for x, _ := range prices { 53 | // AddTickerPrice(ticker.Price, prices[x].CryptoCurrency, prices[x].FiatCurrency, prices[x]) 54 | // } 55 | 56 | // return ticker 57 | // } 58 | 59 | // const ( 60 | // LIMIT_ORDER = iota 61 | // MARKET_ORDER 62 | // ) 63 | 64 | // var Orders []*Order 65 | 66 | // type Order struct { 67 | // OrderID int 68 | // Exchange string 69 | // Type int 70 | // Amount float64 71 | // Price float64 72 | // } 73 | 74 | // func NewOrder(Exchange string, amount, price float64) int { 75 | // order := &Order{} 76 | // if len(Orders) == 0 { 77 | // order.OrderID = 0 78 | // } else { 79 | // order.OrderID = len(Orders) 80 | // } 81 | 82 | // order.Exchange = Exchange 83 | // order.Amount = amount 84 | // order.Price = price 85 | // Orders = append(Orders, order) 86 | // return order.OrderID 87 | // } 88 | 89 | // func DeleteOrder(orderID int) bool { 90 | // for i := range Orders { 91 | // if Orders[i].OrderID == orderID { 92 | // Orders = append(Orders[:i], Orders[i+1:]...) 93 | // return true 94 | // } 95 | // } 96 | // return false 97 | // } 98 | 99 | // func GetOrdersByExchange(exchange string) ([]*Order, bool) { 100 | // orders := []*Order{} 101 | // for i := range Orders { 102 | // if Orders[i].Exchange == exchange { 103 | // orders = append(orders, Orders[i]) 104 | // } 105 | // } 106 | // if len(orders) > 0 { 107 | // return orders, true 108 | // } 109 | // return nil, false 110 | // } 111 | 112 | // func GetOrderByOrderID(orderID int) (*Order, bool) { 113 | // for i := range Orders { 114 | // if Orders[i].OrderID == orderID { 115 | // return Orders[i], true 116 | // } 117 | // } 118 | // return nil, false 119 | // } 120 | -------------------------------------------------------------------------------- /src/robot/configstore.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | ) 10 | 11 | const ( 12 | CONFIG_FILE = "config.json" 13 | ) 14 | 15 | type IConfigStore interface { 16 | Read() (DockerConfig, error) 17 | Write(DockerConfig) error 18 | } 19 | 20 | type FileConfigStore struct { 21 | } 22 | 23 | func (store FileConfigStore) Read() (DockerConfig, error) { 24 | var dockerConfig DockerConfig 25 | file, err := ioutil.ReadFile(CONFIG_FILE) 26 | 27 | if err != nil { 28 | return DockerConfig{}, err 29 | } 30 | 31 | err = json.Unmarshal(file, &dockerConfig) 32 | return dockerConfig, err 33 | } 34 | 35 | func (store FileConfigStore) Write(dockerConfig DockerConfig) (err error) { 36 | log.Println("Saving config to file...") 37 | payload, err := json.MarshalIndent(dockerConfig, "", " ") 38 | 39 | if err != nil { 40 | return err 41 | } 42 | 43 | err = ioutil.WriteFile(CONFIG_FILE, payload, 0644) 44 | 45 | if err != nil { 46 | return err 47 | } 48 | retrieved, err := ioutil.ReadFile(CONFIG_FILE) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | if !bytes.Equal(retrieved, payload) { 54 | return fmt.Errorf("File %q content doesn't match, read %s expected %s\n", CONFIG_FILE, retrieved, payload) 55 | } 56 | 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /src/robot/currency.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "net/url" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | type Rate struct { 13 | Id string `json:"id"` 14 | Name string `json:"Name"` 15 | Rate float64 `json:",string"` 16 | Date string `json:"Date"` 17 | Time string `json:"Time"` 18 | Ask float64 `json:",string"` 19 | Bid float64 `json:",string"` 20 | } 21 | 22 | type YahooJSONResponseInfo struct { 23 | Count int `json:"count"` 24 | Created time.Time `json:"created"` 25 | Lang string `json:"lang"` 26 | } 27 | 28 | type YahooJSONResponse struct { 29 | Query struct { 30 | YahooJSONResponseInfo 31 | Results struct { 32 | Rate []Rate `json:"rate"` 33 | } 34 | } 35 | } 36 | 37 | const ( 38 | MAX_CURRENCY_PAIRS_PER_REQUEST = 350 39 | YAHOO_YQL_URL = "http://query.yahooapis.com/v1/public/yql" 40 | YAHOO_DATABASE = "store://datatables.org/alltableswithkeys" 41 | DEFAULT_CURRENCIES = "USD,AUD,EUR,CNY" 42 | ) 43 | 44 | var ( 45 | CurrencyStore map[string]Rate 46 | BaseCurrencies string 47 | ErrCurrencyDataNotFetched = errors.New("Yahoo currency data has not been fetched yet.") 48 | ErrCurrencyNotFound = errors.New("Unable to find specified currency.") 49 | ErrQueryingYahoo = errors.New("Unable to query Yahoo currency values.") 50 | ErrQueryingYahooZeroCount = errors.New("Yahoo returned zero currency data.") 51 | ) 52 | 53 | // func IsDefaultCurrency(currency string) bool { 54 | // return StringContains(DEFAULT_CURRENCIES, StringToUpper(currency)) 55 | // } 56 | 57 | // func IsFiatCurrency(currency string) bool { 58 | // return StringContains(BaseCurrencies, StringToUpper(currency)) 59 | // } 60 | 61 | // func IsCryptocurrency(currency string) bool { 62 | // return StringContains(docker.Cryptocurrencies, StringToUpper(currency)) 63 | // } 64 | 65 | // func RetrieveConfigCurrencyPairs(config Config) error { 66 | // var fiatCurrencies, cryptoCurrencies []string 67 | // for _, exchange := range config.ExchangeAccounts { 68 | // if exchange.Enabled { 69 | // enabledPairs := SplitStrings(exchange.EnabledPairs, ",") 70 | // baseCurrencies := SplitStrings(exchange.BaseCurrencies, ",") 71 | 72 | // for _, x := range baseCurrencies { 73 | // for _, y := range enabledPairs { 74 | // if StringContains(y, "_") { 75 | // pairs := SplitStrings(y, "_") 76 | // for _, z := range pairs { 77 | // if z != x && !IsCryptocurrency(z) { 78 | // cryptoCurrencies = append(cryptoCurrencies, z) 79 | // } else if z == x && !IsDefaultCurrency(x) { 80 | // fiatCurrencies = append(fiatCurrencies, x) 81 | // } 82 | // } 83 | // } else { 84 | // if StringContains(y, x) { 85 | // if !IsDefaultCurrency(x) { 86 | // fiatCurrencies = append(fiatCurrencies, x) 87 | // } 88 | // currency := TrimString(y, x) 89 | // if !IsCryptocurrency(currency) { 90 | // cryptoCurrencies = append(cryptoCurrencies, currency) 91 | // } 92 | // } else { 93 | // if !IsCryptocurrency(y[0:3]) { 94 | // cryptoCurrencies = append(cryptoCurrencies, y[0:3]) 95 | // } 96 | // if !IsCryptocurrency(y[3:]) { 97 | // cryptoCurrencies = append(cryptoCurrencies, y[3:]) 98 | // } 99 | // } 100 | // } 101 | // } 102 | // } 103 | // } 104 | // } 105 | // config.Cryptocurrencies = JoinStrings(StringSliceDifference(SplitStrings(config.Cryptocurrencies, ","), cryptoCurrencies), ",") 106 | // BaseCurrencies = JoinStrings(StringSliceDifference(SplitStrings(DEFAULT_CURRENCIES, ","), fiatCurrencies), ",") 107 | 108 | // err := QueryYahooCurrencyValues(BaseCurrencies) 109 | 110 | // if err != nil { 111 | // return ErrQueryingYahoo 112 | // } 113 | 114 | // log.Println("Fetched currency value data.") 115 | // return nil 116 | // } 117 | 118 | func MakecurrencyPairs(supportedCurrencies string) string { 119 | currencies := SplitStrings(supportedCurrencies, ",") 120 | pairs := []string{} 121 | count := len(currencies) 122 | for i := 0; i < count; i++ { 123 | currency := currencies[i] 124 | for j := 0; j < count; j++ { 125 | if currency != currencies[j] { 126 | pairs = append(pairs, currency+currencies[j]) 127 | } 128 | } 129 | } 130 | return JoinStrings(pairs, ",") 131 | } 132 | 133 | func ConvertCurrency(amount float64, from, to string) (float64, error) { 134 | currency := StringToUpper(from + to) 135 | for x, y := range CurrencyStore { 136 | if x == currency { 137 | return amount * y.Rate, nil 138 | } 139 | } 140 | return 0, ErrCurrencyNotFound 141 | } 142 | 143 | func FetchYahooCurrencyData(currencyPairs []string) error { 144 | values := url.Values{} 145 | values.Set("q", fmt.Sprintf("SELECT * from yahoo.finance.xchange WHERE pair in (\"%s\")", JoinStrings(currencyPairs, ","))) 146 | values.Set("format", "json") 147 | values.Set("env", YAHOO_DATABASE) 148 | 149 | headers := make(map[string]string) 150 | headers["Content-Type"] = "application/x-www-form-urlencoded" 151 | 152 | resp, err := SendHTTPRequest("POST", YAHOO_YQL_URL, headers, strings.NewReader(values.Encode())) 153 | 154 | if err != nil { 155 | return err 156 | } 157 | 158 | yahooResp := YahooJSONResponse{} 159 | err = JSONDecode([]byte(resp), &yahooResp) 160 | 161 | if err != nil { 162 | return err 163 | } 164 | 165 | if yahooResp.Query.Count == 0 { 166 | return ErrQueryingYahooZeroCount 167 | } 168 | 169 | for i := 0; i < yahooResp.Query.YahooJSONResponseInfo.Count; i++ { 170 | CurrencyStore[yahooResp.Query.Results.Rate[i].Id] = yahooResp.Query.Results.Rate[i] 171 | } 172 | 173 | return nil 174 | } 175 | 176 | func QueryYahooCurrencyValues(currencies string) error { 177 | CurrencyStore = make(map[string]Rate) 178 | currencyPairs := SplitStrings(MakecurrencyPairs(currencies), ",") 179 | log.Printf("%d fiat currency pairs generated. Fetching Yahoo currency data (this may take a minute)..\n", len(currencyPairs)) 180 | var err error 181 | var pairs []string 182 | index := 0 183 | 184 | if len(currencyPairs) > MAX_CURRENCY_PAIRS_PER_REQUEST { 185 | for index < len(currencyPairs) { 186 | if len(currencyPairs)-index > MAX_CURRENCY_PAIRS_PER_REQUEST { 187 | pairs = currencyPairs[index : index+MAX_CURRENCY_PAIRS_PER_REQUEST] 188 | index += MAX_CURRENCY_PAIRS_PER_REQUEST 189 | } else { 190 | pairs = currencyPairs[index:len(currencyPairs)] 191 | index += (len(currencyPairs) - index) 192 | } 193 | err = FetchYahooCurrencyData(pairs) 194 | if err != nil { 195 | return err 196 | } 197 | } 198 | } else { 199 | pairs = currencyPairs[index:len(currencyPairs)] 200 | err = FetchYahooCurrencyData(pairs) 201 | 202 | if err != nil { 203 | return err 204 | } 205 | } 206 | return nil 207 | } 208 | -------------------------------------------------------------------------------- /src/robot/docker.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | ) 7 | 8 | type DockerStatus int 9 | 10 | const ( 11 | DOCKER_CONNECTED DockerStatus = iota 12 | DOCKER_DISCONNECTED 13 | DOCKER_CONNECTING 14 | DOCKER_DISCONNECTING 15 | ) 16 | 17 | type DockerConfig struct { 18 | ID int 19 | ServerAddress string 20 | IP string 21 | Port int 22 | OS string 23 | OSVersion string 24 | Status DockerStatus 25 | IsLocal bool // Is it running on the same machine as server? 26 | Verbose bool 27 | 28 | Cryptocurrencies string 29 | ExchangeConfigs map[string]ExchangeConfig 30 | ExchangeAccountConfigs map[string]ExchangeAccountConfig 31 | StrategieConfigs map[string]StrategyConfig 32 | BotConfigs map[string]BotConfig 33 | } 34 | 35 | type Docker struct { 36 | DockerConfig 37 | Bots []*Bot 38 | } 39 | 40 | var ( 41 | ErrExchangeNameEmpty = "Exchange #%d in config: Exchange name is empty." 42 | ErrExchangeAvailablePairsEmpty = "Exchange %s: Available pairs is empty." 43 | ErrExchangeEnabledPairsEmpty = "Exchange %s: Enabled pairs is empty." 44 | ErrExchangeBaseCurrenciesEmpty = "Exchange %s: Base currencies is empty." 45 | WarningExchangeAuthAPIDefaultOrEmptyValues = "WARNING -- Exchange %s: Authenticated API support disabled due to default/empty APIKey/Secret/ClientID values." 46 | ErrExchangeNotFound = "Exchange %s: Not found." 47 | ErrNoEnabledExchanges = "No Exchanges enabled." 48 | ErrCryptocurrenciesEmpty = "Cryptocurrencies variable is empty." 49 | ) 50 | 51 | func (docker *Docker) CheckConfigValues() error { 52 | // if docker.Cryptocurrencies == "" { 53 | // return errors.New(ErrCryptocurrenciesEmpty) 54 | // } 55 | 56 | // exchanges := 0 57 | // for i, exch := range docker.ExchangeAccounts { 58 | // if exch.Enabled { 59 | // if exch.Name == "" { 60 | // return fmt.Errorf(ErrExchangeNameEmpty, i) 61 | // } 62 | // if exch.AvailablePairs == "" { 63 | // return fmt.Errorf(ErrExchangeAvailablePairsEmpty, exch.Name) 64 | // } 65 | // if exch.AuthenticatedAPISupport { // non-fatal error 66 | // if exch.APIKey == "" || exch.APISecret == "" || exch.APIKey == "Key" || exch.APISecret == "Secret" { 67 | // config.ExchangeAccounts[i].AuthenticatedAPISupport = false 68 | // log.Printf(WarningExchangeAuthAPIDefaultOrEmptyValues, exch.Name) 69 | // continue 70 | // } else if exch.Name == "ITBIT" || exch.Name == "Bitstamp" || exch.Name == "Coinbase" { 71 | // if exch.ClientID == "" || exch.ClientID == "ClientID" { 72 | // config.ExchangeAccounts[i].AuthenticatedAPISupport = false 73 | // log.Printf(WarningExchangeAuthAPIDefaultOrEmptyValues, exch.Name) 74 | // continue 75 | // } 76 | // } 77 | // } 78 | // exchanges++ 79 | // } 80 | // } 81 | // if exchanges == 0 { 82 | // return errors.New(ErrNoEnabledExchanges) 83 | // } 84 | return nil 85 | } 86 | 87 | func (docker *Docker) Stop() { 88 | log.Println("Docker shutting down...") 89 | 90 | for _, bot := range docker.Bots { 91 | if bot.Enabled { 92 | bot.Stop() 93 | } 94 | } 95 | 96 | log.Println("All bots stopped.") 97 | } 98 | 99 | // func (docker *Docker) Login() 100 | // func (docker *Docker) Logout() 101 | 102 | func (docker *Docker) Start() error { 103 | err := docker.CheckConfigValues() 104 | 105 | if err != nil { 106 | log.Println("Fatal error checking config values. Error:", err) 107 | return err 108 | } 109 | 110 | for _, botConfig := range docker.BotConfigs { 111 | var bot Bot 112 | bot.BotConfig = botConfig 113 | bot.docker = docker 114 | docker.Bots = append(docker.Bots, &bot) 115 | 116 | if !bot.Enabled { 117 | continue 118 | } else { 119 | if err := bot.Start(); err != nil { 120 | log.Fatal(err) 121 | } 122 | } 123 | } 124 | return err 125 | } 126 | 127 | func (docker *Docker) version() string { 128 | return "Version:" + version + " Buildstamp:" + buildstamp + " Git Hash:" + githash 129 | } 130 | 131 | func (docker *Docker) SaveConfig() { 132 | //Saving configs 133 | var store IConfigStore 134 | if docker.IsLocal { 135 | store = new(FileConfigStore) 136 | } else { 137 | log.Fatal("No implementation for other config stores.") 138 | return 139 | } 140 | 141 | if err := store.Write(docker.DockerConfig); err != nil { 142 | log.Fatal("Error saving configs to store.") 143 | } 144 | } 145 | 146 | func (docker *Docker) ReadConfig() (err error) { 147 | //Reading configs 148 | var store IConfigStore 149 | store = FileConfigStore{} 150 | 151 | if docker.DockerConfig, err = store.Read(); err != nil { 152 | return 153 | } 154 | 155 | if err == nil && !docker.IsLocal { 156 | return errors.New("No implementation for other config stores now.") 157 | } 158 | 159 | return 160 | } 161 | -------------------------------------------------------------------------------- /src/robot/exchange.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type ExchangeConfig struct { 8 | RESTPollingDelay time.Duration 9 | AvailablePairs string 10 | BaseCurrencies string 11 | } 12 | 13 | //IExchange : Enforces standard functions for all exchanges supported 14 | type IExchange interface { 15 | GetAvailablePairs() []string 16 | GetName() string 17 | Setup(exconfig ExchangeConfig) 18 | Start() 19 | } 20 | 21 | type Fee struct { 22 | Sell float64 //percentage 0.2 for 0.2% 23 | Buy float64 //percentage 24 | } 25 | 26 | type MarketOrder struct { 27 | Price float64 28 | Amount float64 29 | } 30 | 31 | type KLineIntervalType int 32 | 33 | const ( 34 | KLINE_1_MIN KLineIntervalType = iota 35 | KLINE_5_MIN 36 | KLINE_15_MIN 37 | KLINE_30_MIN 38 | KLINE_1_HOUR 39 | KLINE_1_DAY 40 | ) 41 | 42 | type Depth struct { 43 | Asks []MarketOrder 44 | Bids []MarketOrder 45 | } 46 | 47 | type OrderType int 48 | 49 | const ( 50 | ORDER_TYPE_BUY = iota 51 | ORDER_TYPE_SELL 52 | ) 53 | 54 | type Trade struct { 55 | Time time.Time 56 | Price float64 57 | Amount float64 58 | Type OrderType 59 | } 60 | 61 | type Record struct { 62 | Time time.Time 63 | Open float64 64 | High float64 65 | Low float64 66 | Close float64 67 | Volume float64 68 | } 69 | 70 | type Order struct { 71 | ID string 72 | Price float64 73 | Amount float64 74 | DealAmount float64 75 | Status OrderStatus 76 | Type OrderType 77 | } 78 | 79 | type OrderStatus int 80 | 81 | const ( 82 | ORDER_STATE_PENDING = iota 83 | ORDER_STATE_CLOSED 84 | ORDER_STATE_CANCELED 85 | ) 86 | 87 | type Ticker struct { 88 | High float64 89 | Low float64 90 | Sell float64 91 | Buy float64 92 | Last float64 93 | Volume float64 94 | } 95 | 96 | type ExchangeAccountInfo struct { 97 | Balance float64 //Fiat 98 | FrozenBalance float64 99 | Stocks float64 //Crypto 100 | FrozenStocks float64 101 | } 102 | 103 | type PositionType int 104 | 105 | const ( 106 | PD_LONG PositionType = iota 107 | PD_SHORT 108 | ) 109 | 110 | type Position struct { 111 | MarginLevel int 112 | Amount float64 113 | //CanCover //Only for stock 114 | FrozenAmount float64 115 | Price float64 116 | Profit float64 117 | Type PositionType 118 | ContractType string 119 | } 120 | -------------------------------------------------------------------------------- /src/robot/exchangeaccount.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type ExchangeAccountConfig struct { 4 | ExchangeName string 5 | Label string 6 | APIKey string 7 | APISecret string 8 | ClientID string 9 | EnabledPair string 10 | AuthenticatedAPISupport bool 11 | DisplayCurrency string 12 | //LoanAPISupport bool 13 | //FutureAPISupport 14 | //MarginAPISupport 15 | //WithdrawAPISupport 16 | } 17 | 18 | //IExchange : Enforces standard functions for all exchanges supported 19 | type IExchangeAccount interface { 20 | 21 | //######################spot exchange interface###################### 22 | GetName() string 23 | GetLabel() string 24 | 25 | //GetUSDCNY()float64 26 | //GetRate() float64 27 | //SetRate(rate float64) 28 | // GetCurrency() string 29 | GetTicker() Ticker 30 | //GetDepth() []Depth 31 | // GetTrades() []Trade 32 | // GetRecords(KLineIntervalType) []Record 33 | GetAccount() (ExchangeAccountInfo, error) 34 | // LimitBuy(price , amount float64) 35 | // LimitSell(price , amount float64) 36 | // MarketBuy( price float64) 37 | // MarketSell( amount float64) 38 | // GetOrders() []Order 39 | // GetOrder(orderID string) Order 40 | // CancelOrder(orderID string) bool 41 | 42 | //######################future exchange interface###################### 43 | //返回一个Position数组, (BitVC和OKCoin)可以传入一个参数, 指定要获取的合约类型 44 | //GetPosition() // 获取当前持仓信息 45 | 46 | //SetMarginLevel(MarginLevel) //设置杆杠大小 47 | 48 | //Direction可以取buy, closebuy, sell, closesell四个参数, 传统期货多出closebuy_today,与closesell_today, 指平今仓, 默认为closebuy/closesell为平咋仓 49 | //SetDirection(Direction) //设置Buy或者Sell下单类型 50 | 51 | //数字货币796支持: "week", "weekcny", 默认为子账户A, 要指定子账户是A还是B, 在合约后加"@A"或"@B", 如: "day@A" 为日合约A子账户 52 | //BitVC有week和quarter和next_week三个可选参数, OKCoin期货有this_week, next_week, quarter三个参数 53 | //SetContractType(ContractType) 设置合约类型 54 | 55 | //期货交易中Buy, Sell, CancelOrder和现货交易的区别 56 | //Buy或Sell之前需要调用SetMarginLevel和SetDirection明确操作类型 57 | //数字货币796的CancelOrder之前需要调用SetDirection明确订单类型 58 | //如: exchange.SetDirection("sell"); exchange.Sell(1000, 2); 59 | //如: exchange.SetDirection("buy"); exchange.CancelOrder(123); 60 | 61 | //GetFuturesTicker() 62 | //GetFuturesDepth() 63 | //OKCoinFuturesTrades 64 | //GetFuturesTrades() 65 | //GetFuturesIndex 66 | //GetFuturesExchangeRate() 67 | //GetFuturesEstimatedPrice() 68 | //GetFutureRecords() 69 | //GetFuturesHoldAmount() 70 | //GetFuturesExplosive() 71 | // 72 | 73 | //######################Exchange Scope interface###################### 74 | // GetMinStock() float64 75 | // GetMinPrice() float64 76 | // GetFee() Fee 77 | //GetRawJSON 78 | 79 | //Support: GetTicker, GetDepth, GetTrades, GetRecords, GetAccount, GetOrders, GetOrder, CancelOrder, Buy, Sell, GetPosition 80 | //Go(Method, Args...) 81 | 82 | //IO("api",apiName string, args interface{}) //this is original from botvs 83 | //API(apiName string, args interface{}) string 84 | 85 | //above functions are exported to VM 86 | GetEnabledPair() string 87 | Start() 88 | SetDefaults() 89 | Setup(ExchangeAccountConfig) 90 | } 91 | -------------------------------------------------------------------------------- /src/robot/huobiaccount.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type HUOBIExchangeAccount struct { 4 | ExchangeAccountConfig 5 | huobi *HUOBIExchange 6 | } 7 | 8 | func (h *HUOBIExchangeAccount) Setup(acctConfig ExchangeAccountConfig) { 9 | h.ExchangeAccountConfig = acctConfig 10 | //ToDo this should be changed 11 | h.huobi = new(HUOBIExchange) 12 | } 13 | func (h *HUOBIExchangeAccount) GetName() string { 14 | return h.ExchangeName 15 | } 16 | 17 | func (h *HUOBIExchangeAccount) GetLabel() string { 18 | return h.Label 19 | } 20 | 21 | func (h *HUOBIExchangeAccount) GetAccount() (ExchangeAccountInfo, error) { 22 | return ExchangeAccountInfo{}, nil 23 | } 24 | 25 | func (h *HUOBIExchangeAccount) GetEnabledPair() string { 26 | return h.EnabledPair 27 | } 28 | 29 | func (h *HUOBIExchangeAccount) Start() { 30 | 31 | } 32 | 33 | func (h *HUOBIExchangeAccount) SetDefaults() { 34 | 35 | } 36 | 37 | func (h *HUOBIExchangeAccount) GetTicker() Ticker { 38 | huobiTicker := h.huobi.GetTicker("BTCCNY") 39 | ticker := Ticker{} 40 | 41 | ticker.Buy = huobiTicker.Buy 42 | ticker.High = huobiTicker.High 43 | ticker.Last = huobiTicker.Last 44 | ticker.Low = huobiTicker.Low 45 | ticker.Sell = huobiTicker.Sell 46 | ticker.Volume = huobiTicker.Vol 47 | 48 | return ticker 49 | } 50 | -------------------------------------------------------------------------------- /src/robot/huobihttp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/url" 7 | "strconv" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | const ( 13 | HUOBI_API_URL = "https://api.huobi.com/apiv2.php" 14 | HUOBI_API_VERSION = "2" 15 | ) 16 | 17 | type HUOBIExchange struct { 18 | Name string 19 | Enabled bool 20 | Verbose bool 21 | Websocket bool 22 | RESTPollingDelay time.Duration 23 | AuthenticatedAPISupport bool 24 | AccessKey, SecretKey string 25 | Fee float64 26 | BaseCurrencies []string 27 | AvailablePairs []string 28 | EnabledPairs []string 29 | } 30 | 31 | type HuobiTicker struct { 32 | High float64 33 | Low float64 34 | Last float64 35 | Vol float64 36 | Buy float64 37 | Sell float64 38 | } 39 | 40 | type HuobiTickerResponse struct { 41 | Time string 42 | Ticker HuobiTicker 43 | } 44 | 45 | func (h *HUOBIExchange) SetDefaults() { 46 | h.Name = "Huobi" 47 | h.Enabled = false 48 | h.Fee = 0 49 | h.Verbose = false 50 | h.Websocket = false 51 | h.RESTPollingDelay = 10 52 | } 53 | 54 | func (h *HUOBIExchange) GetName() string { 55 | return h.Name 56 | } 57 | 58 | func (h *HUOBIExchange) SetEnabled(enabled bool) { 59 | h.Enabled = enabled 60 | } 61 | 62 | func (h *HUOBIExchange) IsEnabled() bool { 63 | return h.Enabled 64 | } 65 | 66 | func (h *HUOBIExchange) Setup(exConfig ExchangeConfig) { 67 | h.RESTPollingDelay = exConfig.RESTPollingDelay 68 | h.BaseCurrencies = SplitStrings(exConfig.BaseCurrencies, ",") 69 | h.AvailablePairs = SplitStrings(exConfig.AvailablePairs, ",") 70 | } 71 | 72 | func (k *HUOBIExchange) GetAvailablePairs() []string { 73 | return k.AvailablePairs 74 | } 75 | 76 | func (h *HUOBIExchange) Start() { 77 | go h.Run() 78 | } 79 | 80 | func (h *HUOBIExchange) SetAPIKeys(apiKey, apiSecret string) { 81 | h.AccessKey = apiKey 82 | h.SecretKey = apiSecret 83 | } 84 | 85 | func (h *HUOBIExchange) GetFee() float64 { 86 | return h.Fee 87 | } 88 | 89 | func (h *HUOBIExchange) Run() { 90 | if h.Verbose { 91 | log.Printf("%s polling delay: %ds.\n", h.GetName(), h.RESTPollingDelay) 92 | log.Printf("%s %d currencies enabled: %s.\n", h.GetName(), len(h.EnabledPairs), h.EnabledPairs) 93 | } 94 | 95 | for h.Enabled { 96 | for _, x := range h.EnabledPairs { 97 | currency := StringToLower(x[0:3]) 98 | go func() { 99 | ticker := h.GetTicker(currency) 100 | HuobiLastUSD, _ := ConvertCurrency(ticker.Last, "CNY", "USD") 101 | HuobiHighUSD, _ := ConvertCurrency(ticker.High, "CNY", "USD") 102 | HuobiLowUSD, _ := ConvertCurrency(ticker.Low, "CNY", "USD") 103 | log.Printf("Huobi %s: Last %f (%f) High %f (%f) Low %f (%f) Volume %f\n", currency, HuobiLastUSD, ticker.Last, HuobiHighUSD, ticker.High, HuobiLowUSD, ticker.Low, ticker.Vol) 104 | //AddExchangeInfo(h.GetName(), StringToUpper(currency[0:3]), StringToUpper(currency[3:]), ticker.Last, ticker.Vol) 105 | //AddExchangeInfo(h.GetName(), StringToUpper(currency[0:3]), "USD", HuobiLastUSD, ticker.Vol) 106 | }() 107 | } 108 | time.Sleep(time.Second * h.RESTPollingDelay) 109 | } 110 | } 111 | 112 | func (h *HUOBIExchange) GetTicker(symbol string) HuobiTicker { 113 | resp := HuobiTickerResponse{} 114 | path := fmt.Sprintf("http://api.huobi.com/staticmarket/ticker_%s_json.js", symbol) 115 | err := SendHTTPGetRequest(path, true, &resp) 116 | 117 | if err != nil { 118 | log.Println(err) 119 | return HuobiTicker{} 120 | } 121 | return resp.Ticker 122 | } 123 | 124 | // func (h *HUOBI) GetTickerPrice(currency string) TickerPrice { 125 | // var tickerPrice TickerPrice 126 | // ticker := h.GetTicker(currency) 127 | // tickerPrice.Ask = ticker.Sell 128 | // tickerPrice.Bid = ticker.Buy 129 | // tickerPrice.CryptoCurrency = currency 130 | // tickerPrice.Low = ticker.Low 131 | // tickerPrice.Last = ticker.Last 132 | // tickerPrice.Volume = ticker.Vol 133 | // tickerPrice.High = ticker.High 134 | 135 | // return tickerPrice 136 | // } 137 | 138 | func (h *HUOBIExchange) GetOrderBook(symbol string) bool { 139 | path := fmt.Sprintf("http://api.huobi.com/staticmarket/depth_%s_json.js", symbol) 140 | err := SendHTTPGetRequest(path, true, nil) 141 | if err != nil { 142 | log.Println(err) 143 | return false 144 | } 145 | return true 146 | } 147 | 148 | func (h *HUOBIExchange) GetAccountInfo() { 149 | err := h.SendAuthenticatedRequest("get_account_info", url.Values{}) 150 | 151 | if err != nil { 152 | log.Println(err) 153 | } 154 | } 155 | 156 | func (h *HUOBIExchange) GetOrders(coinType int) { 157 | values := url.Values{} 158 | values.Set("coin_type", strconv.Itoa(coinType)) 159 | err := h.SendAuthenticatedRequest("get_orders", values) 160 | 161 | if err != nil { 162 | log.Println(err) 163 | } 164 | } 165 | 166 | func (h *HUOBIExchange) GetOrderInfo(orderID, coinType int) { 167 | values := url.Values{} 168 | values.Set("id", strconv.Itoa(orderID)) 169 | values.Set("coin_type", strconv.Itoa(coinType)) 170 | err := h.SendAuthenticatedRequest("order_info", values) 171 | 172 | if err != nil { 173 | log.Println(err) 174 | } 175 | } 176 | 177 | func (h *HUOBIExchange) Trade(orderType string, coinType int, price, amount float64) { 178 | values := url.Values{} 179 | if orderType != "buy" { 180 | orderType = "sell" 181 | } 182 | values.Set("coin_type", strconv.Itoa(coinType)) 183 | values.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) 184 | values.Set("price", strconv.FormatFloat(price, 'f', -1, 64)) 185 | err := h.SendAuthenticatedRequest(orderType, values) 186 | 187 | if err != nil { 188 | log.Println(err) 189 | } 190 | } 191 | 192 | func (h *HUOBIExchange) MarketTrade(orderType string, coinType int, price, amount float64) { 193 | values := url.Values{} 194 | if orderType != "buy_market" { 195 | orderType = "sell_market" 196 | } 197 | values.Set("coin_type", strconv.Itoa(coinType)) 198 | values.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) 199 | values.Set("price", strconv.FormatFloat(price, 'f', -1, 64)) 200 | err := h.SendAuthenticatedRequest(orderType, values) 201 | 202 | if err != nil { 203 | log.Println(err) 204 | } 205 | } 206 | 207 | func (h *HUOBIExchange) CancelOrder(orderID, coinType int) { 208 | values := url.Values{} 209 | values.Set("coin_type", strconv.Itoa(coinType)) 210 | values.Set("id", strconv.Itoa(orderID)) 211 | err := h.SendAuthenticatedRequest("cancel_order", values) 212 | 213 | if err != nil { 214 | log.Println(err) 215 | } 216 | } 217 | 218 | func (h *HUOBIExchange) ModifyOrder(orderType string, coinType, orderID int, price, amount float64) { 219 | values := url.Values{} 220 | values.Set("coin_type", strconv.Itoa(coinType)) 221 | values.Set("id", strconv.Itoa(orderID)) 222 | values.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) 223 | values.Set("price", strconv.FormatFloat(price, 'f', -1, 64)) 224 | err := h.SendAuthenticatedRequest("modify_order", values) 225 | 226 | if err != nil { 227 | log.Println(err) 228 | } 229 | } 230 | 231 | func (h *HUOBIExchange) GetNewDealOrders(coinType int) { 232 | values := url.Values{} 233 | values.Set("coin_type", strconv.Itoa(coinType)) 234 | err := h.SendAuthenticatedRequest("get_new_deal_orders", values) 235 | 236 | if err != nil { 237 | log.Println(err) 238 | } 239 | } 240 | 241 | func (h *HUOBIExchange) GetOrderIDByTradeID(coinType, orderID int) { 242 | values := url.Values{} 243 | values.Set("coin_type", strconv.Itoa(coinType)) 244 | values.Set("trade_id", strconv.Itoa(orderID)) 245 | err := h.SendAuthenticatedRequest("get_order_id_by_trade_id", values) 246 | 247 | if err != nil { 248 | log.Println(err) 249 | } 250 | } 251 | 252 | func (h *HUOBIExchange) SendAuthenticatedRequest(method string, v url.Values) error { 253 | v.Set("access_key", h.AccessKey) 254 | v.Set("created", strconv.FormatInt(time.Now().Unix(), 10)) 255 | v.Set("method", method) 256 | hash := GetMD5([]byte(v.Encode() + "&secret_key=" + h.SecretKey)) 257 | v.Set("sign", strings.ToLower(HexEncodeToString(hash))) 258 | encoded := v.Encode() 259 | 260 | if h.Verbose { 261 | log.Printf("Sending POST request to %s with params %s\n", HUOBI_API_URL, encoded) 262 | } 263 | 264 | headers := make(map[string]string) 265 | headers["Content-Type"] = "application/x-www-form-urlencoded" 266 | 267 | resp, err := SendHTTPRequest("POST", HUOBI_API_URL, headers, strings.NewReader(encoded)) 268 | 269 | if err != nil { 270 | return err 271 | } 272 | 273 | if h.Verbose { 274 | log.Printf("Recieved raw: %s\n", resp) 275 | } 276 | 277 | return nil 278 | } 279 | 280 | //TODO: retrieve HUOBI balance info 281 | //GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the HUOBI exchange 282 | func (e *HUOBIExchange) GetExchangeAccountInfo() (ExchangeAccountInfo, error) { 283 | var response ExchangeAccountInfo 284 | return response, nil 285 | } 286 | -------------------------------------------------------------------------------- /src/robot/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | _ "net/http/pprof" 6 | "os" 7 | "os/signal" 8 | "runtime" 9 | "strconv" 10 | "syscall" 11 | ) 12 | 13 | var verbose bool 14 | 15 | var version = "No Version Provided" 16 | var buildstamp = "No Buildstamp Provided" 17 | var githash = "No Githash Provided" 18 | var docker *Docker = &Docker{} 19 | 20 | func main() { 21 | var shutdownChan chan bool 22 | 23 | log.Println("Version:" + version + " Buildstamp:" + buildstamp + " Git Hash:" + githash) 24 | handleInterrupt() 25 | 26 | //verbose = true 27 | 28 | log.Println("Loading config file config.json..") 29 | err := docker.ReadConfig() 30 | if err != nil { 31 | log.Printf("Fatal error opening config.json file. Error: %s", err) 32 | return 33 | } 34 | verbose = docker.Verbose 35 | 36 | log.Println("Config file loaded. Checking settings.. ") 37 | 38 | AdjustGoMaxProcs() 39 | docker.Start() 40 | 41 | log.Println("Press Ctrl-C to quit.") 42 | <-shutdownChan 43 | } 44 | 45 | func AdjustGoMaxProcs() { 46 | log.Println("Adjusting bot runtime performance..") 47 | maxProcsEnv := os.Getenv("GOMAXPROCS") 48 | maxProcs := runtime.NumCPU() 49 | log.Println("Number of CPU's detected:", maxProcs) 50 | 51 | if maxProcsEnv != "" { 52 | log.Println("GOMAXPROCS env =", maxProcsEnv) 53 | env, err := strconv.Atoi(maxProcsEnv) 54 | 55 | if err != nil { 56 | log.Println("Unable to convert GOMAXPROCS to int, using", maxProcs) 57 | } else { 58 | maxProcs = env 59 | } 60 | } 61 | log.Println("Set GOMAXPROCS to:", maxProcs) 62 | runtime.GOMAXPROCS(maxProcs) 63 | } 64 | 65 | func handleInterrupt() { 66 | c := make(chan os.Signal, 1) 67 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 68 | go func() { 69 | sig := <-c 70 | log.Printf("Captured %v.", sig) 71 | docker.SaveConfig() 72 | docker.Stop() 73 | os.Exit(1) 74 | }() 75 | } 76 | -------------------------------------------------------------------------------- /src/robot/okcoinaccount.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type OKCoinExchangeAccount struct { 4 | ExchangeAccountConfig 5 | okcoin *OKCoinExchange 6 | symbol string 7 | } 8 | 9 | func (ok *OKCoinExchangeAccount) Setup(acctConfig ExchangeAccountConfig) { 10 | ok.ExchangeAccountConfig = acctConfig 11 | ok.okcoin = new(OKCoinExchange) 12 | if ok.ExchangeAccountConfig.ExchangeName == "OKCOINUSD" { 13 | ok.okcoin.SetURL(OKCOIN_API_URL) 14 | } else { 15 | ok.okcoin.SetURL(OKCOIN_API_URL_CHINA) 16 | } 17 | } 18 | func (ok *OKCoinExchangeAccount) GetName() string { 19 | return ok.ExchangeName 20 | } 21 | 22 | func (ok *OKCoinExchangeAccount) GetLabel() string { 23 | return ok.Label 24 | } 25 | 26 | func (ok *OKCoinExchangeAccount) GetAccount() (ExchangeAccountInfo, error) { 27 | return ExchangeAccountInfo{}, nil 28 | } 29 | 30 | func (ok *OKCoinExchangeAccount) GetEnabledPair() string { 31 | return ok.EnabledPair 32 | } 33 | 34 | func (ok *OKCoinExchangeAccount) Start() { 35 | 36 | } 37 | 38 | func (ok *OKCoinExchangeAccount) SetDefaults() { 39 | 40 | } 41 | 42 | func (ok *OKCoinExchangeAccount) GetTicker() Ticker { 43 | okTicker, err := ok.okcoin.GetTicker("BTCCNY") 44 | if err != nil { 45 | return Ticker{} 46 | } 47 | ticker := Ticker{} 48 | 49 | ticker.Buy = okTicker.Buy 50 | ticker.High = okTicker.High 51 | ticker.Last = okTicker.Last 52 | ticker.Low = okTicker.Low 53 | ticker.Sell = okTicker.Sell 54 | ticker.Volume = okTicker.Vol 55 | 56 | return ticker 57 | } 58 | -------------------------------------------------------------------------------- /src/robot/okcoinhttp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "net/url" 7 | "strconv" 8 | "strings" 9 | "time" 10 | 11 | "github.com/gorilla/websocket" 12 | ) 13 | 14 | const ( 15 | OKCOIN_API_URL = "https://www.okcoin.com/api/v1/" 16 | OKCOIN_API_URL_CHINA = "https://www.okcoin.cn/api/v1/" 17 | OKCOIN_API_VERSION = "1" 18 | OKCOIN_WEBSOCKET_URL = "wss://real.okcoin.com:10440/websocket/okcoinapi" 19 | OKCOIN_WEBSOCKET_URL_CHINA = "wss://real.okcoin.cn:10440/websocket/okcoinapi" 20 | OKCOIN_TICKER = "ticker.do" 21 | OKCOIN_DEPTH = "depth.do" 22 | OKCOIN_TRADES = "trades.do" 23 | OKCOIN_KLINE = "kline.do" 24 | OKCOIN_USERINFO = "userinfo.do" 25 | OKCOIN_TRADE = "trade.do" 26 | OKCOIN_TRADE_HISTORY = "trade_history.do" 27 | OKCOIN_TRADE_BATCH = "batch_trade.do" 28 | OKCOIN_ORDER_CANCEL = "cancel_order.do" 29 | OKCOIN_ORDER_INFO = "order_info.do" 30 | OKCOIN_ORDERS_INFO = "orders_info.do" 31 | OKCOIN_ORDER_HISTORY = "order_history.do" 32 | OKCOIN_WITHDRAW = "withdraw.do" 33 | OKCOIN_WITHDRAW_CANCEL = "cancel_withdraw.do" 34 | OKCOIN_WITHDRAW_INFO = "withdraw_info.do" 35 | OKCOIN_ORDER_FEE = "order_fee.do" 36 | OKCOIN_LEND_DEPTH = "lend_depth.do" 37 | OKCOIN_BORROWS_INFO = "borrows_info.do" 38 | OKCOIN_BORROW_MONEY = "borrow_money.do" 39 | OKCOIN_BORROW_CANCEL = "cancel_borrow.do" 40 | OKCOIN_BORROW_ORDER_INFO = "borrow_order_info.do" 41 | OKCOIN_REPAYMENT = "repayment.do" 42 | OKCOIN_UNREPAYMENTS_INFO = "unrepayments_info.do" 43 | OKCOIN_ACCOUNT_RECORDS = "account_records.do" 44 | OKCOIN_FUTURES_TICKER = "future_ticker.do" 45 | OKCOIN_FUTURES_DEPTH = "future_depth.do" 46 | OKCOIN_FUTURES_TRADES = "future_trades.do" 47 | OKCOIN_FUTURES_INDEX = "future_index.do" 48 | OKCOIN_EXCHANGE_RATE = "exchange_rate.do" 49 | OKCOIN_FUTURES_ESTIMATED_PRICE = "future_estimated_price.do" 50 | OKCOIN_FUTURES_KLINE = "future_kline.do" 51 | OKCOIN_FUTURES_HOLD_AMOUNT = "future_hold_amount.do" 52 | OKCOIN_FUTURES_USERINFO = "future_userinfo.do" 53 | OKCOIN_FUTURES_POSITION = "future_position.do" 54 | OKCOIN_FUTURES_TRADE = "future_trade.do" 55 | OKCOIN_FUTURES_TRADE_HISTORY = "future_trades_history.do" 56 | OKCOIN_FUTURES_TRADE_BATCH = "future_batch_trade.do" 57 | OKCOIN_FUTURES_CANCEL = "future_cancel.do" 58 | OKCOIN_FUTURES_ORDER_INFO = "future_order_info.do" 59 | OKCOIN_FUTURES_ORDERS_INFO = "future_orders_info.do" 60 | OKCOIN_FUTURES_USERINFO_4FIX = "future_userinfo_4fix.do" 61 | OKCOIN_FUTURES_POSITION_4FIX = "future_position_4fix.do" 62 | OKCOIN_FUTURES_EXPLOSIVE = "future_explosive.do" 63 | OKCOIN_FUTURES_DEVOLVE = "future_devolve.do" 64 | ) 65 | 66 | var ( 67 | okcoinDefaultsSet = false 68 | ) 69 | 70 | type OKCoinExchange struct { 71 | Name string 72 | Enabled bool 73 | Verbose bool 74 | Websocket bool 75 | WebsocketURL string 76 | RESTPollingDelay time.Duration 77 | AuthenticatedAPISupport bool 78 | APIUrl, PartnerID, SecretKey string 79 | TakerFee, MakerFee float64 80 | RESTErrors map[string]string 81 | WebsocketErrors map[string]string 82 | BaseCurrencies []string 83 | AvailablePairs []string 84 | EnabledPairs []string 85 | FuturesValues []string 86 | WebsocketConn *websocket.Conn 87 | } 88 | 89 | type OKCoinTicker struct { 90 | Buy float64 `json:",string"` 91 | High float64 `json:",string"` 92 | Last float64 `json:",string"` 93 | Low float64 `json:",string"` 94 | Sell float64 `json:",string"` 95 | Vol float64 `json:",string"` 96 | } 97 | 98 | type OKCoinTickerResponse struct { 99 | Date string 100 | Ticker OKCoinTicker 101 | } 102 | 103 | type OKCoinFuturesTicker struct { 104 | Last float64 105 | Buy float64 106 | Sell float64 107 | High float64 108 | Low float64 109 | Vol float64 110 | Contract_ID int64 111 | Unit_Amount float64 112 | } 113 | 114 | type OKCoinOrderbook struct { 115 | Asks [][]float64 `json:"asks"` 116 | Bids [][]float64 `json:"bids"` 117 | } 118 | 119 | type OKCoinFuturesTickerResponse struct { 120 | Date string 121 | Ticker OKCoinFuturesTicker 122 | } 123 | 124 | type OKCoinBorrowInfo struct { 125 | BorrowBTC float64 `json:"borrow_btc"` 126 | BorrowLTC float64 `json:"borrow_ltc"` 127 | BorrowCNY float64 `json:"borrow_cny"` 128 | CanBorrow float64 `json:"can_borrow"` 129 | InterestBTC float64 `json:"interest_btc"` 130 | InterestLTC float64 `json:"interest_ltc"` 131 | Result bool `json:"result"` 132 | DailyInterestBTC float64 `json:"today_interest_btc"` 133 | DailyInterestLTC float64 `json:"today_interest_ltc"` 134 | DailyInterestCNY float64 `json:"today_interest_cny"` 135 | } 136 | 137 | type OKCoinBorrowOrder struct { 138 | Amount float64 `json:"amount"` 139 | BorrowDate int64 `json:"borrow_date"` 140 | BorrowID int64 `json:"borrow_id"` 141 | Days int64 `json:"days"` 142 | TradeAmount float64 `json:"deal_amount"` 143 | Rate float64 `json:"rate"` 144 | Status int64 `json:"status"` 145 | Symbol string `json:"symbol"` 146 | } 147 | 148 | type OKCoinRecord struct { 149 | Address string `json:"addr"` 150 | Account int64 `json:"account,string"` 151 | Amount float64 `json:"amount"` 152 | Bank string `json:"bank"` 153 | BenificiaryAddress string `json:"benificiary_addr"` 154 | TransactionValue float64 `json:"transaction_value"` 155 | Fee float64 `json:"fee"` 156 | Date float64 `json:"date"` 157 | } 158 | 159 | type OKCoinAccountRecords struct { 160 | Records []OKCoinRecord `json:"records"` 161 | Symbol string `json:"symbol"` 162 | } 163 | 164 | type OKCoinFuturesOrder struct { 165 | Amount float64 `json:"amount"` 166 | ContractName string `json:"contract_name"` 167 | DateCreated float64 `json:"create_date"` 168 | TradeAmount float64 `json:"deal_amount"` 169 | Fee float64 `json:"fee"` 170 | LeverageRate float64 `json:"lever_rate"` 171 | OrderID int64 `json:"order_id"` 172 | Price float64 `json:"price"` 173 | AvgPrice float64 `json:"avg_price"` 174 | Status float64 `json:"status"` 175 | Symbol string `json:"symbol"` 176 | Type int64 `json:"type"` 177 | UnitAmount int64 `json:"unit_amount"` 178 | } 179 | 180 | type OKCoinFuturesHoldAmount struct { 181 | Amount float64 `json:"amount"` 182 | ContractName string `json:"contract_name"` 183 | } 184 | 185 | type OKCoinFuturesExplosive struct { 186 | Amount float64 `json:"amount,string"` 187 | DateCreated string `json:"create_date"` 188 | Loss float64 `json:"loss,string"` 189 | Type int64 `json:"type"` 190 | } 191 | 192 | func (o *OKCoinExchange) GetName() string { 193 | return o.Name 194 | } 195 | 196 | func (o *OKCoinExchange) SetURL(url string) { 197 | o.APIUrl = url 198 | } 199 | 200 | func (o *OKCoinExchange) Setup(exConfig ExchangeConfig) { 201 | o.RESTPollingDelay = exConfig.RESTPollingDelay 202 | o.BaseCurrencies = SplitStrings(exConfig.BaseCurrencies, ",") 203 | o.AvailablePairs = SplitStrings(exConfig.AvailablePairs, ",") 204 | } 205 | 206 | func (k *OKCoinExchange) GetAvailablePairs() []string { 207 | return k.AvailablePairs 208 | } 209 | 210 | func (o *OKCoinExchange) Start() { 211 | go o.Run() 212 | } 213 | 214 | func (o *OKCoinExchange) GetFee(maker bool) float64 { 215 | if o.APIUrl == OKCOIN_API_URL { 216 | if maker { 217 | return o.MakerFee 218 | } else { 219 | return o.TakerFee 220 | } 221 | } 222 | // Chinese exchange does not have any trading fees 223 | return 0 224 | } 225 | 226 | func (o *OKCoinExchange) Run() { 227 | if o.Verbose { 228 | log.Printf("%s Websocket: %s. (url: %s).\n", o.GetName(), IsEnabled(o.Websocket), o.WebsocketURL) 229 | log.Printf("%s polling delay: %ds.\n", o.GetName(), o.RESTPollingDelay) 230 | log.Printf("%s %d currencies enabled: %s.\n", o.GetName(), len(o.EnabledPairs), o.EnabledPairs) 231 | } 232 | 233 | for o.Enabled { 234 | for _, x := range o.EnabledPairs { 235 | currency := StringToLower(x[0:3] + "_" + x[3:]) 236 | if o.APIUrl == OKCOIN_API_URL { 237 | for _, y := range o.FuturesValues { 238 | futuresValue := y 239 | go func() { 240 | ticker, err := o.GetFuturesTicker(currency, futuresValue) 241 | if err != nil { 242 | log.Println(err) 243 | return 244 | } 245 | log.Printf("OKCoin Intl Futures %s (%s): Last %f High %f Low %f Volume %f\n", currency, futuresValue, ticker.Last, ticker.High, ticker.Low, ticker.Vol) 246 | //AddExchangeInfo(o.GetName(), StringToUpper(currency[0:3]), StringToUpper(currency[4:]), ticker.Last, ticker.Vol) 247 | }() 248 | } 249 | go func() { 250 | ticker, err := o.GetTicker(currency) 251 | if err != nil { 252 | log.Println(err) 253 | return 254 | } 255 | log.Printf("OKCoin Intl Spot %s: Last %f High %f Low %f Volume %f\n", currency, ticker.Last, ticker.High, ticker.Low, ticker.Vol) 256 | //AddExchangeInfo(o.GetName(), StringToUpper(currency[0:3]), StringToUpper(currency[4:]), ticker.Last, ticker.Vol) 257 | }() 258 | } else { 259 | go func() { 260 | ticker, err := o.GetTicker(currency) 261 | if err != nil { 262 | log.Println(err) 263 | return 264 | } 265 | tickerLastUSD, _ := ConvertCurrency(ticker.Last, "CNY", "USD") 266 | tickerHighUSD, _ := ConvertCurrency(ticker.High, "CNY", "USD") 267 | tickerLowUSD, _ := ConvertCurrency(ticker.Low, "CNY", "USD") 268 | log.Printf("OKCoin China %s: Last %f (%f) High %f (%f) Low %f (%f) Volume %f\n", currency, tickerLastUSD, ticker.Last, tickerHighUSD, ticker.High, tickerLowUSD, ticker.Low, ticker.Vol) 269 | //AddExchangeInfo(o.GetName(), StringToUpper(currency[0:3]), StringToUpper(currency[4:]), ticker.Last, ticker.Vol) 270 | //AddExchangeInfo(o.GetName(), StringToUpper(currency[0:3]), "USD", tickerLastUSD, ticker.Vol) 271 | }() 272 | } 273 | } 274 | time.Sleep(time.Second * o.RESTPollingDelay) 275 | } 276 | } 277 | 278 | func (o *OKCoinExchange) GetTicker(symbol string) (OKCoinTicker, error) { 279 | resp := OKCoinTickerResponse{} 280 | vals := url.Values{} 281 | vals.Set("symbol", symbol) 282 | path := EncodeURLValues(o.APIUrl+OKCOIN_TICKER, vals) 283 | err := SendHTTPGetRequest(path, true, &resp) 284 | if err != nil { 285 | return OKCoinTicker{}, err 286 | } 287 | return resp.Ticker, nil 288 | } 289 | 290 | func (o *OKCoinExchange) GetOrderBook(symbol string, size int64, merge bool) (OKCoinOrderbook, error) { 291 | resp := OKCoinOrderbook{} 292 | vals := url.Values{} 293 | vals.Set("symbol", symbol) 294 | if size != 0 { 295 | vals.Set("size", strconv.FormatInt(size, 10)) 296 | } 297 | if merge { 298 | vals.Set("merge", "1") 299 | } 300 | 301 | path := EncodeURLValues(o.APIUrl+OKCOIN_DEPTH, vals) 302 | err := SendHTTPGetRequest(path, true, &resp) 303 | if err != nil { 304 | return resp, err 305 | } 306 | return resp, nil 307 | } 308 | 309 | type OKCoinTrades struct { 310 | Amount float64 `json:"amount,string"` 311 | Date int64 `json:"date` 312 | DateMS int64 `json:"date_ms"` 313 | Price float64 `json:"price,string"` 314 | TradeID int64 `json:"tid"` 315 | Type string `json:"type"` 316 | } 317 | 318 | func (o *OKCoinExchange) GetTrades(symbol string, since int64) ([]OKCoinTrades, error) { 319 | result := []OKCoinTrades{} 320 | vals := url.Values{} 321 | vals.Set("symbol", symbol) 322 | if since != 0 { 323 | vals.Set("since", strconv.FormatInt(since, 10)) 324 | } 325 | 326 | path := EncodeURLValues(o.APIUrl+OKCOIN_TRADES, vals) 327 | err := SendHTTPGetRequest(path, true, &result) 328 | if err != nil { 329 | return nil, err 330 | } 331 | return result, nil 332 | } 333 | 334 | func (o *OKCoinExchange) GetKline(symbol, klineType string, size, since int64) ([]interface{}, error) { 335 | resp := []interface{}{} 336 | vals := url.Values{} 337 | vals.Set("symbol", symbol) 338 | vals.Set("type", klineType) 339 | 340 | if size != 0 { 341 | vals.Set("size", strconv.FormatInt(size, 10)) 342 | } 343 | 344 | if since != 0 { 345 | vals.Set("since", strconv.FormatInt(since, 10)) 346 | } 347 | 348 | path := EncodeURLValues(o.APIUrl+OKCOIN_KLINE, vals) 349 | err := SendHTTPGetRequest(path, true, &resp) 350 | if err != nil { 351 | return nil, err 352 | } 353 | 354 | return resp, nil 355 | } 356 | 357 | func (o *OKCoinExchange) GetFuturesTicker(symbol, contractType string) (OKCoinFuturesTicker, error) { 358 | resp := OKCoinFuturesTickerResponse{} 359 | vals := url.Values{} 360 | vals.Set("symbol", symbol) 361 | vals.Set("contract_type", contractType) 362 | path := EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_TICKER, vals) 363 | err := SendHTTPGetRequest(path, true, &resp) 364 | if err != nil { 365 | return OKCoinFuturesTicker{}, err 366 | } 367 | return resp.Ticker, nil 368 | } 369 | 370 | func (o *OKCoinExchange) GetFuturesDepth(symbol, contractType string, size int64, merge bool) (OKCoinOrderbook, error) { 371 | result := OKCoinOrderbook{} 372 | vals := url.Values{} 373 | vals.Set("symbol", symbol) 374 | vals.Set("contract_type", contractType) 375 | 376 | if size != 0 { 377 | vals.Set("size", strconv.FormatInt(size, 10)) 378 | } 379 | if merge { 380 | vals.Set("merge", "1") 381 | } 382 | 383 | path := EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_DEPTH, vals) 384 | err := SendHTTPGetRequest(path, true, &result) 385 | if err != nil { 386 | return result, err 387 | } 388 | return result, nil 389 | } 390 | 391 | type OKCoinFuturesTrades struct { 392 | Amount float64 `json:"amount"` 393 | Date int64 `json:"date"` 394 | DateMS int64 `json:"date_ms"` 395 | Price float64 `json:"price"` 396 | TradeID int64 `json:"tid"` 397 | Type string `json:"type"` 398 | } 399 | 400 | func (o *OKCoinExchange) GetFuturesTrades(symbol, contractType string) ([]OKCoinFuturesTrades, error) { 401 | result := []OKCoinFuturesTrades{} 402 | vals := url.Values{} 403 | vals.Set("symbol", symbol) 404 | vals.Set("contract_type", contractType) 405 | 406 | path := EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_TRADES, vals) 407 | err := SendHTTPGetRequest(path, true, &result) 408 | if err != nil { 409 | return nil, err 410 | } 411 | return result, nil 412 | } 413 | 414 | func (o *OKCoinExchange) GetFuturesIndex(symbol string) (float64, error) { 415 | type Response struct { 416 | Index float64 `json:"future_index"` 417 | } 418 | 419 | result := Response{} 420 | vals := url.Values{} 421 | vals.Set("symbol", symbol) 422 | 423 | path := EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_INDEX, vals) 424 | err := SendHTTPGetRequest(path, true, &result) 425 | if err != nil { 426 | return 0, err 427 | } 428 | return result.Index, nil 429 | } 430 | 431 | func (o *OKCoinExchange) GetFuturesExchangeRate() (float64, error) { 432 | type Response struct { 433 | Rate float64 `json:"rate"` 434 | } 435 | 436 | result := Response{} 437 | err := SendHTTPGetRequest(o.APIUrl+OKCOIN_EXCHANGE_RATE, true, &result) 438 | if err != nil { 439 | return result.Rate, err 440 | } 441 | return result.Rate, nil 442 | } 443 | 444 | func (o *OKCoinExchange) GetFuturesEstimatedPrice(symbol string) (float64, error) { 445 | type Response struct { 446 | Price float64 `json:"forecast_price"` 447 | } 448 | 449 | result := Response{} 450 | vals := url.Values{} 451 | vals.Set("symbol", symbol) 452 | path := EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_ESTIMATED_PRICE, vals) 453 | err := SendHTTPGetRequest(path, true, &result) 454 | if err != nil { 455 | return result.Price, err 456 | } 457 | return result.Price, nil 458 | } 459 | 460 | func (o *OKCoinExchange) GetFuturesKline(symbol, klineType, contractType string, size, since int64) ([]interface{}, error) { 461 | resp := []interface{}{} 462 | vals := url.Values{} 463 | vals.Set("symbol", symbol) 464 | vals.Set("type", klineType) 465 | vals.Set("contract_type", contractType) 466 | 467 | if size != 0 { 468 | vals.Set("size", strconv.FormatInt(size, 10)) 469 | } 470 | if since != 0 { 471 | vals.Set("since", strconv.FormatInt(since, 10)) 472 | } 473 | 474 | path := EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_KLINE, vals) 475 | err := SendHTTPGetRequest(path, true, &resp) 476 | 477 | if err != nil { 478 | return nil, err 479 | } 480 | return resp, nil 481 | } 482 | 483 | func (o *OKCoinExchange) GetFuturesHoldAmount(symbol, contractType string) ([]OKCoinFuturesHoldAmount, error) { 484 | resp := []OKCoinFuturesHoldAmount{} 485 | vals := url.Values{} 486 | vals.Set("symbol", symbol) 487 | vals.Set("contract_type", contractType) 488 | 489 | path := EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_HOLD_AMOUNT, vals) 490 | err := SendHTTPGetRequest(path, true, &resp) 491 | 492 | if err != nil { 493 | return nil, err 494 | } 495 | return resp, nil 496 | } 497 | 498 | func (o *OKCoinExchange) GetFuturesExplosive(symbol, contractType string, status, currentPage, pageLength int64) ([]OKCoinFuturesExplosive, error) { 499 | type Response struct { 500 | Data []OKCoinFuturesExplosive `json:"data"` 501 | } 502 | resp := Response{} 503 | vals := url.Values{} 504 | vals.Set("symbol", symbol) 505 | vals.Set("contract_type", contractType) 506 | vals.Set("status", strconv.FormatInt(status, 10)) 507 | vals.Set("current_page", strconv.FormatInt(currentPage, 10)) 508 | vals.Set("page_length", strconv.FormatInt(pageLength, 10)) 509 | 510 | path := EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_EXPLOSIVE, vals) 511 | err := SendHTTPGetRequest(path, true, &resp) 512 | 513 | if err != nil { 514 | return nil, err 515 | } 516 | 517 | return resp.Data, nil 518 | } 519 | 520 | type OKCoinUserInfo struct { 521 | Info struct { 522 | Funds struct { 523 | Asset struct { 524 | Net float64 `json:"net,string"` 525 | Total float64 `json:"total,string"` 526 | } `json:"asset"` 527 | Borrow struct { 528 | BTC float64 `json:"btc,string"` 529 | LTC float64 `json:"ltc,string"` 530 | USD float64 `json:"usd,string"` 531 | } `json:"borrow"` 532 | Free struct { 533 | BTC float64 `json:"btc,string"` 534 | LTC float64 `json:"ltc,string"` 535 | USD float64 `json:"usd,string"` 536 | } `json:"free"` 537 | Freezed struct { 538 | BTC float64 `json:"btc,string"` 539 | LTC float64 `json:"ltc,string"` 540 | USD float64 `json:"usd,string"` 541 | } `json:"freezed"` 542 | UnionFund struct { 543 | BTC float64 `json:"btc,string"` 544 | LTC float64 `json:"ltc,string"` 545 | } `json:"union_fund"` 546 | } `json:"funds"` 547 | } `json:"info"` 548 | Result bool `json:"result"` 549 | } 550 | 551 | func (o *OKCoinExchange) GetUserInfo() (OKCoinUserInfo, error) { 552 | result := OKCoinUserInfo{} 553 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_USERINFO, url.Values{}, &result) 554 | 555 | if err != nil { 556 | return result, err 557 | } 558 | 559 | return result, nil 560 | } 561 | 562 | func (o *OKCoinExchange) Trade(amount, price float64, symbol, orderType string) (int64, error) { 563 | type Response struct { 564 | Result bool `json:"result"` 565 | OrderID int64 `json:"order_id"` 566 | } 567 | v := url.Values{} 568 | v.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) 569 | v.Set("price", strconv.FormatFloat(price, 'f', -1, 64)) 570 | v.Set("symbol", symbol) 571 | v.Set("type", orderType) 572 | 573 | result := Response{} 574 | 575 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_TRADE, v, &result) 576 | 577 | if err != nil { 578 | return 0, err 579 | } 580 | 581 | if !result.Result { 582 | return 0, errors.New("Unable to place order.") 583 | } 584 | 585 | return result.OrderID, nil 586 | } 587 | 588 | func (o *OKCoinExchange) GetTradeHistory(symbol string, TradeID int64) ([]OKCoinTrades, error) { 589 | result := []OKCoinTrades{} 590 | v := url.Values{} 591 | v.Set("symbol", symbol) 592 | v.Set("since", strconv.FormatInt(TradeID, 10)) 593 | 594 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_TRADE_HISTORY, v, &result) 595 | 596 | if err != nil { 597 | return nil, err 598 | } 599 | 600 | return result, nil 601 | } 602 | 603 | type OKCoinBatchTrade struct { 604 | OrderInfo []struct { 605 | OrderID int64 `json:"order_id"` 606 | ErrorCode int64 `json:"error_code"` 607 | } `json:"order_info"` 608 | Result bool `json:"result"` 609 | } 610 | 611 | func (o *OKCoinExchange) BatchTrade(orderData string, symbol, orderType string) (OKCoinBatchTrade, error) { 612 | v := url.Values{} 613 | v.Set("orders_data", orderData) 614 | v.Set("symbol", symbol) 615 | v.Set("type", orderType) 616 | result := OKCoinBatchTrade{} 617 | 618 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_TRADE_BATCH, v, &result) 619 | 620 | if err != nil { 621 | return result, err 622 | } 623 | 624 | return result, nil 625 | } 626 | 627 | type OKCoinCancelOrderResponse struct { 628 | Success string 629 | Error string 630 | } 631 | 632 | func (o *OKCoinExchange) CancelOrder(orderID []int64, symbol string) (OKCoinCancelOrderResponse, error) { 633 | v := url.Values{} 634 | orders := []string{} 635 | orderStr := "" 636 | result := OKCoinCancelOrderResponse{} 637 | 638 | if len(orderID) > 1 { 639 | for x := range orderID { 640 | orders = append(orders, strconv.FormatInt(orderID[x], 10)) 641 | } 642 | orderStr = JoinStrings(orders, ",") 643 | } else { 644 | orderStr = strconv.FormatInt(orderID[0], 10) 645 | } 646 | 647 | v.Set("order_id", orderStr) 648 | v.Set("symbol", symbol) 649 | 650 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_ORDER_CANCEL, v, &result) 651 | 652 | if err != nil { 653 | return result, err 654 | } 655 | 656 | return result, nil 657 | } 658 | 659 | type OKCoinOrderInfo struct { 660 | Amount float64 `json:"amount"` 661 | AvgPrice float64 `json:"avg_price"` 662 | Created int64 `json:"create_date"` 663 | DealAmount float64 `json:"deal_amount"` 664 | OrderID int64 `json:"order_id"` 665 | OrdersID int64 `json:"orders_id"` 666 | Price float64 `json:"price"` 667 | Status int `json:"status"` 668 | Symbol string `json:"symbol"` 669 | Type string `json:"type"` 670 | } 671 | 672 | func (o *OKCoinExchange) GetOrderInfo(orderID int64, symbol string) ([]OKCoinOrderInfo, error) { 673 | type Response struct { 674 | Result bool `json:"result"` 675 | Orders []OKCoinOrderInfo `json:"orders"` 676 | } 677 | v := url.Values{} 678 | v.Set("symbol", symbol) 679 | v.Set("order_id", strconv.FormatInt(orderID, 10)) 680 | result := Response{} 681 | 682 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_ORDER_INFO, v, &result) 683 | 684 | if err != nil { 685 | return nil, err 686 | } 687 | 688 | if result.Result != true { 689 | return nil, errors.New("Unable to retrieve order info.") 690 | } 691 | 692 | return result.Orders, nil 693 | } 694 | 695 | func (o *OKCoinExchange) GetOrderInfoBatch(orderID []int64, symbol string) ([]OKCoinOrderInfo, error) { 696 | type Response struct { 697 | Result bool `json:"result"` 698 | Orders []OKCoinOrderInfo `json:"orders"` 699 | } 700 | 701 | orders := []string{} 702 | for x := range orderID { 703 | orders = append(orders, strconv.FormatInt(orderID[x], 10)) 704 | } 705 | 706 | v := url.Values{} 707 | v.Set("symbol", symbol) 708 | v.Set("order_id", JoinStrings(orders, ",")) 709 | result := Response{} 710 | 711 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_ORDER_INFO, v, &result) 712 | 713 | if err != nil { 714 | return nil, err 715 | } 716 | 717 | if result.Result != true { 718 | return nil, errors.New("Unable to retrieve order info.") 719 | } 720 | 721 | return result.Orders, nil 722 | } 723 | 724 | type OKCoinOrderHistory struct { 725 | CurrentPage int `json:"current_page"` 726 | Orders []OKCoinOrderInfo `json:"orders"` 727 | PageLength int `json:"page_length"` 728 | Result bool `json:"result"` 729 | Total int `json:"total"` 730 | } 731 | 732 | func (o *OKCoinExchange) GetOrderHistory(pageLength, currentPage int64, status, symbol string) (OKCoinOrderHistory, error) { 733 | v := url.Values{} 734 | v.Set("symbol", symbol) 735 | v.Set("status", status) 736 | v.Set("current_page", strconv.FormatInt(currentPage, 10)) 737 | v.Set("page_length", strconv.FormatInt(pageLength, 10)) 738 | result := OKCoinOrderHistory{} 739 | 740 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_ORDER_HISTORY, v, &result) 741 | 742 | if err != nil { 743 | return result, err 744 | } 745 | 746 | return result, nil 747 | } 748 | 749 | type OKCoinWithdrawalResponse struct { 750 | WithdrawID int `json:"withdraw_id"` 751 | Result bool `json:"result"` 752 | } 753 | 754 | func (o *OKCoinExchange) Withdrawal(symbol string, fee float64, tradePWD, address string, amount float64) (int, error) { 755 | v := url.Values{} 756 | v.Set("symbol", symbol) 757 | 758 | if fee != 0 { 759 | v.Set("chargefee", strconv.FormatFloat(fee, 'f', -1, 64)) 760 | } 761 | v.Set("trade_pwd", tradePWD) 762 | v.Set("withdraw_address", address) 763 | v.Set("withdraw_amount", strconv.FormatFloat(amount, 'f', -1, 64)) 764 | result := OKCoinWithdrawalResponse{} 765 | 766 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_WITHDRAW, v, &result) 767 | 768 | if err != nil { 769 | return 0, err 770 | } 771 | 772 | if !result.Result { 773 | return 0, errors.New("Unable to process withdrawal request.") 774 | } 775 | 776 | return result.WithdrawID, nil 777 | } 778 | 779 | func (o *OKCoinExchange) CancelWithdrawal(symbol string, withdrawalID int64) (int, error) { 780 | v := url.Values{} 781 | v.Set("symbol", symbol) 782 | v.Set("withdrawal_id", strconv.FormatInt(withdrawalID, 10)) 783 | result := OKCoinWithdrawalResponse{} 784 | 785 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_WITHDRAW_CANCEL, v, &result) 786 | 787 | if err != nil { 788 | return 0, err 789 | } 790 | 791 | if !result.Result { 792 | return 0, errors.New("Unable to process withdrawal cancel request.") 793 | } 794 | 795 | return result.WithdrawID, nil 796 | } 797 | 798 | type OKCoinWithdrawInfo struct { 799 | Address string `json:"address"` 800 | Amount float64 `json:"amount"` 801 | Created int64 `json:"created_date"` 802 | ChargeFee float64 `json:"chargefee"` 803 | Status int `json:"status"` 804 | WithdrawID int64 `json:"withdraw_id"` 805 | } 806 | 807 | func (o *OKCoinExchange) GetWithdrawalInfo(symbol string, withdrawalID int64) ([]OKCoinWithdrawInfo, error) { 808 | type Response struct { 809 | Result bool 810 | Withdraw []OKCoinWithdrawInfo `json:"withdraw"` 811 | } 812 | v := url.Values{} 813 | v.Set("symbol", symbol) 814 | v.Set("withdrawal_id", strconv.FormatInt(withdrawalID, 10)) 815 | result := Response{} 816 | 817 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_WITHDRAW_INFO, v, &result) 818 | 819 | if err != nil { 820 | return nil, err 821 | } 822 | 823 | if !result.Result { 824 | return nil, errors.New("Unable to process withdrawal cancel request.") 825 | } 826 | 827 | return result.Withdraw, nil 828 | } 829 | 830 | type OKCoinOrderFeeInfo struct { 831 | Fee float64 `json:"fee,string"` 832 | OrderID int64 `json:"order_id"` 833 | Type string `json:"type"` 834 | } 835 | 836 | func (o *OKCoinExchange) GetOrderFeeInfo(symbol string, orderID int64) (OKCoinOrderFeeInfo, error) { 837 | type Response struct { 838 | Data OKCoinOrderFeeInfo `json:"data"` 839 | Result bool `json:"result"` 840 | } 841 | 842 | v := url.Values{} 843 | v.Set("symbol", symbol) 844 | v.Set("order_id", strconv.FormatInt(orderID, 10)) 845 | result := Response{} 846 | 847 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_ORDER_FEE, v, &result) 848 | 849 | if err != nil { 850 | return result.Data, err 851 | } 852 | 853 | if !result.Result { 854 | return result.Data, errors.New("Unable to get order fee info.") 855 | } 856 | 857 | return result.Data, nil 858 | } 859 | 860 | type OKCoinLendDepth struct { 861 | Amount float64 `json:"amount"` 862 | Days string `json:"days"` 863 | Num int64 `json:"num"` 864 | Rate float64 `json:"rate,string"` 865 | } 866 | 867 | func (o *OKCoinExchange) GetLendDepth(symbol string) ([]OKCoinLendDepth, error) { 868 | type Response struct { 869 | LendDepth []OKCoinLendDepth `json:"lend_depth"` 870 | } 871 | 872 | v := url.Values{} 873 | v.Set("symbol", symbol) 874 | result := Response{} 875 | 876 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_LEND_DEPTH, v, &result) 877 | 878 | if err != nil { 879 | return nil, err 880 | } 881 | 882 | return result.LendDepth, nil 883 | } 884 | 885 | func (o *OKCoinExchange) GetBorrowInfo(symbol string) (OKCoinBorrowInfo, error) { 886 | v := url.Values{} 887 | v.Set("symbol", symbol) 888 | result := OKCoinBorrowInfo{} 889 | 890 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_BORROWS_INFO, v, &result) 891 | 892 | if err != nil { 893 | return result, nil 894 | } 895 | 896 | return result, nil 897 | } 898 | 899 | type OKCoinBorrowResponse struct { 900 | Result bool `json:"result"` 901 | BorrowID int `json:"borrow_id"` 902 | } 903 | 904 | func (o *OKCoinExchange) Borrow(symbol, days string, amount, rate float64) (int, error) { 905 | v := url.Values{} 906 | v.Set("symbol", symbol) 907 | v.Set("days", days) 908 | v.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) 909 | v.Set("rate", strconv.FormatFloat(rate, 'f', -1, 64)) 910 | result := OKCoinBorrowResponse{} 911 | 912 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_BORROW_MONEY, v, &result) 913 | 914 | if err != nil { 915 | return 0, err 916 | } 917 | 918 | if !result.Result { 919 | return 0, errors.New("Unable to borrow.") 920 | } 921 | 922 | return result.BorrowID, nil 923 | } 924 | 925 | func (o *OKCoinExchange) CancelBorrow(symbol string, borrowID int64) (bool, error) { 926 | v := url.Values{} 927 | v.Set("symbol", symbol) 928 | v.Set("borrow_id", strconv.FormatInt(borrowID, 10)) 929 | result := OKCoinBorrowResponse{} 930 | 931 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_BORROW_CANCEL, v, &result) 932 | 933 | if err != nil { 934 | return false, err 935 | } 936 | 937 | if !result.Result { 938 | return false, errors.New("Unable to cancel borrow.") 939 | } 940 | 941 | return true, nil 942 | } 943 | 944 | func (o *OKCoinExchange) GetBorrowOrderInfo(borrowID int64) (OKCoinBorrowInfo, error) { 945 | type Response struct { 946 | Result bool `json:"result"` 947 | BorrowOrder OKCoinBorrowInfo `json:"borrow_order"` 948 | } 949 | 950 | v := url.Values{} 951 | v.Set("borrow_id", strconv.FormatInt(borrowID, 10)) 952 | result := Response{} 953 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_BORROW_ORDER_INFO, v, &result) 954 | 955 | if err != nil { 956 | return result.BorrowOrder, err 957 | } 958 | 959 | if !result.Result { 960 | return result.BorrowOrder, errors.New("Unable to get borrow info.") 961 | } 962 | 963 | return result.BorrowOrder, nil 964 | } 965 | 966 | func (o *OKCoinExchange) GetRepaymentInfo(borrowID int64) (bool, error) { 967 | v := url.Values{} 968 | v.Set("borrow_id", strconv.FormatInt(borrowID, 10)) 969 | result := OKCoinBorrowResponse{} 970 | 971 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_REPAYMENT, v, &result) 972 | 973 | if err != nil { 974 | return false, err 975 | } 976 | 977 | if !result.Result { 978 | return false, errors.New("Unable to get repayment info.") 979 | } 980 | 981 | return true, nil 982 | } 983 | 984 | func (o *OKCoinExchange) GetUnrepaymentsInfo(symbol string, currentPage, pageLength int) ([]OKCoinBorrowOrder, error) { 985 | type Response struct { 986 | Unrepayments []OKCoinBorrowOrder `json:"unrepayments"` 987 | Result bool `json:"result"` 988 | } 989 | v := url.Values{} 990 | v.Set("symbol", symbol) 991 | v.Set("current_page", strconv.Itoa(currentPage)) 992 | v.Set("page_length", strconv.Itoa(pageLength)) 993 | result := Response{} 994 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_UNREPAYMENTS_INFO, v, &result) 995 | 996 | if err != nil { 997 | return nil, err 998 | } 999 | 1000 | if !result.Result { 1001 | return nil, errors.New("Unable to get unrepayments info.") 1002 | } 1003 | 1004 | return result.Unrepayments, nil 1005 | } 1006 | 1007 | func (o *OKCoinExchange) GetAccountRecords(symbol string, recType, currentPage, pageLength int) ([]OKCoinAccountRecords, error) { 1008 | type Response struct { 1009 | Records []OKCoinAccountRecords `json:"records"` 1010 | Symbol string `json:"symbol"` 1011 | } 1012 | v := url.Values{} 1013 | v.Set("symbol", symbol) 1014 | v.Set("type", strconv.Itoa(recType)) 1015 | v.Set("current_page", strconv.Itoa(currentPage)) 1016 | v.Set("page_length", strconv.Itoa(pageLength)) 1017 | result := Response{} 1018 | 1019 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_ACCOUNT_RECORDS, v, &result) 1020 | 1021 | if err != nil { 1022 | return nil, err 1023 | } 1024 | 1025 | return result.Records, nil 1026 | } 1027 | 1028 | //TODO support for retrieving holdings from OKCOIN 1029 | //GetExchangeAccountInfo : Retrieves balances for all enabled currencies for the OKCoin exchange 1030 | func (e *OKCoinExchange) GetExchangeAccountInfo() (ExchangeAccountInfo, error) { 1031 | var response ExchangeAccountInfo 1032 | return response, nil 1033 | } 1034 | 1035 | func (o *OKCoinExchange) GetFuturesUserInfo() { 1036 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_FUTURES_USERINFO, url.Values{}, nil) 1037 | 1038 | if err != nil { 1039 | log.Println(err) 1040 | } 1041 | } 1042 | 1043 | func (o *OKCoinExchange) GetFuturesPosition(symbol, contractType string) { 1044 | v := url.Values{} 1045 | v.Set("symbol", symbol) 1046 | v.Set("contract_type", contractType) 1047 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_FUTURES_POSITION, v, nil) 1048 | 1049 | if err != nil { 1050 | log.Println(err) 1051 | } 1052 | } 1053 | 1054 | func (o *OKCoinExchange) FuturesTrade(amount, price float64, matchPrice, leverage int64, symbol, contractType, orderType string) { 1055 | v := url.Values{} 1056 | v.Set("symbol", symbol) 1057 | v.Set("contract_type", contractType) 1058 | v.Set("price", strconv.FormatFloat(price, 'f', -1, 64)) 1059 | v.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) 1060 | v.Set("type", orderType) 1061 | v.Set("match_price", strconv.FormatInt(matchPrice, 10)) 1062 | v.Set("lever_rate", strconv.FormatInt(leverage, 10)) 1063 | 1064 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_FUTURES_TRADE, v, nil) 1065 | 1066 | if err != nil { 1067 | log.Println(err) 1068 | } 1069 | } 1070 | 1071 | func (o *OKCoinExchange) FuturesBatchTrade(orderData, symbol, contractType string, leverage int64, orderType string) { 1072 | v := url.Values{} //to-do batch trade support for orders_data) 1073 | v.Set("symbol", symbol) 1074 | v.Set("contract_type", contractType) 1075 | v.Set("orders_data", orderData) 1076 | v.Set("lever_rate", strconv.FormatInt(leverage, 10)) 1077 | 1078 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_FUTURES_TRADE_BATCH, v, nil) 1079 | 1080 | if err != nil { 1081 | log.Println(err) 1082 | } 1083 | } 1084 | 1085 | func (o *OKCoinExchange) CancelFuturesOrder(orderID int64, symbol, contractType string) { 1086 | v := url.Values{} 1087 | v.Set("symbol", symbol) 1088 | v.Set("contract_type", contractType) 1089 | v.Set("order_id", strconv.FormatInt(orderID, 10)) 1090 | 1091 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_FUTURES_CANCEL, v, nil) 1092 | 1093 | if err != nil { 1094 | log.Println(err) 1095 | } 1096 | } 1097 | 1098 | func (o *OKCoinExchange) GetFuturesOrderInfo(orderID, status, currentPage, pageLength int64, symbol, contractType string) { 1099 | v := url.Values{} 1100 | v.Set("symbol", symbol) 1101 | v.Set("contract_type", contractType) 1102 | v.Set("status", strconv.FormatInt(status, 10)) 1103 | v.Set("order_id", strconv.FormatInt(orderID, 10)) 1104 | v.Set("current_page", strconv.FormatInt(currentPage, 10)) 1105 | v.Set("page_length", strconv.FormatInt(pageLength, 10)) 1106 | 1107 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_FUTURES_ORDER_INFO, v, nil) 1108 | 1109 | if err != nil { 1110 | log.Println(err) 1111 | } 1112 | } 1113 | 1114 | func (o *OKCoinExchange) GetFutureOrdersInfo(orderID int64, contractType, symbol string) { 1115 | v := url.Values{} 1116 | v.Set("order_id", strconv.FormatInt(orderID, 10)) 1117 | v.Set("contract_type", contractType) 1118 | v.Set("symbol", symbol) 1119 | 1120 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_FUTURES_ORDERS_INFO, v, nil) 1121 | 1122 | if err != nil { 1123 | log.Println(err) 1124 | } 1125 | } 1126 | 1127 | func (o *OKCoinExchange) GetFuturesUserInfo4Fix() { 1128 | v := url.Values{} 1129 | 1130 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_FUTURES_USERINFO_4FIX, v, nil) 1131 | 1132 | if err != nil { 1133 | log.Println(err) 1134 | } 1135 | } 1136 | 1137 | func (o *OKCoinExchange) GetFuturesUserPosition4Fix(symbol, contractType string) { 1138 | v := url.Values{} 1139 | v.Set("symbol", symbol) 1140 | v.Set("contract_type", contractType) 1141 | v.Set("type", strconv.FormatInt(1, 10)) 1142 | 1143 | err := o.SendAuthenticatedHTTPRequest(OKCOIN_FUTURES_POSITION_4FIX, v, nil) 1144 | 1145 | if err != nil { 1146 | log.Println(err) 1147 | } 1148 | } 1149 | 1150 | func (o *OKCoinExchange) SendAuthenticatedHTTPRequest(method string, v url.Values, result interface{}) (err error) { 1151 | v.Set("api_key", o.PartnerID) 1152 | hasher := GetMD5([]byte(v.Encode() + "&secret_key=" + o.SecretKey)) 1153 | v.Set("sign", strings.ToUpper(HexEncodeToString(hasher))) 1154 | 1155 | encoded := v.Encode() 1156 | path := o.APIUrl + method 1157 | 1158 | if o.Verbose { 1159 | log.Printf("Sending POST request to %s with params %s\n", path, encoded) 1160 | } 1161 | 1162 | headers := make(map[string]string) 1163 | headers["Content-Type"] = "application/x-www-form-urlencoded" 1164 | 1165 | resp, err := SendHTTPRequest("POST", path, headers, strings.NewReader(encoded)) 1166 | 1167 | if err != nil { 1168 | return err 1169 | } 1170 | 1171 | if o.Verbose { 1172 | log.Printf("Recieved raw: \n%s\n", resp) 1173 | } 1174 | 1175 | err = JSONDecode([]byte(resp), &result) 1176 | 1177 | if err != nil { 1178 | return errors.New("Unable to JSON Unmarshal response.") 1179 | } 1180 | 1181 | return nil 1182 | } 1183 | 1184 | func (o *OKCoinExchange) SetErrorDefaults() { 1185 | o.RESTErrors = map[string]string{ 1186 | "10000": "Required field, can not be null", 1187 | "10001": "Request frequency too high", 1188 | "10002": "System error", 1189 | "10003": "Not in reqest list, please try again later", 1190 | "10004": "IP not allowed to access the resource", 1191 | "10005": "'secretKey' does not exist", 1192 | "10006": "'partner' does not exist", 1193 | "10007": "Signature does not match", 1194 | "10008": "Illegal parameter", 1195 | "10009": "Order does not exist", 1196 | "10010": "Insufficient funds", 1197 | "10011": "Amount too low", 1198 | "10012": "Only btc_usd/btc_cny ltc_usd,ltc_cny supported", 1199 | "10013": "Only support https request", 1200 | "10014": "Order price must be between 0 and 1,000,000", 1201 | "10015": "Order price differs from current market price too much", 1202 | "10016": "Insufficient coins balance", 1203 | "10017": "API authorization error", 1204 | "10018": "Borrow amount less than lower limit [usd/cny:100,btc:0.1,ltc:1]", 1205 | "10019": "Loan agreement not checked", 1206 | "10020": `Rate cannot exceed 1%`, 1207 | "10021": `Rate cannot less than 0.01%`, 1208 | "10023": "Fail to get latest ticker", 1209 | "10024": "Balance not sufficient", 1210 | "10025": "Quota is full, cannot borrow temporarily", 1211 | "10026": "Loan (including reserved loan) and margin cannot be withdrawn", 1212 | "10027": "Cannot withdraw within 24 hrs of authentication information modification", 1213 | "10028": "Withdrawal amount exceeds daily limit", 1214 | "10029": "Account has unpaid loan, please cancel/pay off the loan before withdraw", 1215 | "10031": "Deposits can only be withdrawn after 6 confirmations", 1216 | "10032": "Please enabled phone/google authenticator", 1217 | "10033": "Fee higher than maximum network transaction fee", 1218 | "10034": "Fee lower than minimum network transaction fee", 1219 | "10035": "Insufficient BTC/LTC", 1220 | "10036": "Withdrawal amount too low", 1221 | "10037": "Trade password not set", 1222 | "10040": "Withdrawal cancellation fails", 1223 | "10041": "Withdrawal address not approved", 1224 | "10042": "Admin password error", 1225 | "10043": "Account equity error, withdrawal failure", 1226 | "10044": "fail to cancel borrowing order", 1227 | "10047": "This function is disabled for sub-account", 1228 | "10100": "User account frozen", 1229 | "10216": "Non-available API", 1230 | "20001": "User does not exist", 1231 | "20002": "Account frozen", 1232 | "20003": "Account frozen due to liquidation", 1233 | "20004": "Futures account frozen", 1234 | "20005": "User futures account does not exist", 1235 | "20006": "Required field missing", 1236 | "20007": "Illegal parameter", 1237 | "20008": "Futures account balance is too low", 1238 | "20009": "Future contract status error", 1239 | "20010": "Risk rate ratio does not exist", 1240 | "20011": `Risk rate higher than 90% before opening position`, 1241 | "20012": `Risk rate higher than 90% after opening position`, 1242 | "20013": "Temporally no counter party price", 1243 | "20014": "System error", 1244 | "20015": "Order does not exist", 1245 | "20016": "Close amount bigger than your open positions", 1246 | "20017": "Not authorized/illegal operation", 1247 | "20018": `Order price differ more than 5% from the price in the last minute`, 1248 | "20019": "IP restricted from accessing the resource", 1249 | "20020": "secretKey does not exist", 1250 | "20021": "Index information does not exist", 1251 | "20022": "Wrong API interface (Cross margin mode shall call cross margin API, fixed margin mode shall call fixed margin API)", 1252 | "20023": "Account in fixed-margin mode", 1253 | "20024": "Signature does not match", 1254 | "20025": "Leverage rate error", 1255 | "20026": "API Permission Error", 1256 | "20027": "No transaction record", 1257 | "20028": "No such contract", 1258 | } 1259 | } 1260 | -------------------------------------------------------------------------------- /src/robot/sample-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": 0, 3 | "ServerAddress": "", 4 | "IP": "127.0.0.1", 5 | "Port": 8080, 6 | "OS": "Mac", 7 | "OSVersion": "Latest", 8 | "Status": 0, 9 | "IsLocal": true, 10 | "Verbose": true, 11 | "Cryptocurrencies": "BTC,LTC,ETH,ETC", 12 | "ExchangeConfigs": { 13 | "BTCC": { 14 | "RESTPollingDelay": 5000, 15 | "AvailablePairs": "BTCCNY,LTCCNY,LTCBTC", 16 | "BaseCurrencies": "CNY" 17 | }, 18 | "HUOBI": { 19 | "RESTPollingDelay": 5000, 20 | "AvailablePairs": "BTCCNY,LTCCNY", 21 | "BaseCurrencies": "CNY" 22 | }, 23 | "OKCOINCNY": { 24 | "RESTPollingDelay": 5000, 25 | "AvailablePairs": "BTCCNY,LTCCNY", 26 | "BaseCurrencies": "CNY" 27 | }, 28 | "OKCOINUSD": { 29 | "RESTPollingDelay": 5000, 30 | "AvailablePairs": "BTCUSD,LTCUSD", 31 | "BaseCurrencies": "USD" 32 | } 33 | }, 34 | "ExchangeAccountConfigs": { 35 | "0": { 36 | "ExchangeName": "BTCC", 37 | "Label": "BTCC-Tong", 38 | "APIKey": "", 39 | "APISecret": "", 40 | "ClientID": "", 41 | "EnabledPair": "BTCCNY", 42 | "AuthenticatedAPISupport": true, 43 | "DisplayCurrency": "CNY" 44 | }, 45 | "1": { 46 | "ExchangeName": "HUOBI", 47 | "Label": "Huobi-", 48 | "APIKey": "Key", 49 | "APISecret": "Secret", 50 | "ClientID": "", 51 | "EnabledPair": "BTCCNY", 52 | "AuthenticatedAPISupport": false, 53 | "DisplayCurrency": "CNY" 54 | }, 55 | "2": { 56 | "ExchangeName": "OKCOINCNY", 57 | "Label": "OKChina-", 58 | "APIKey": "Key", 59 | "APISecret": "Secret", 60 | "ClientID": "", 61 | "EnabledPair": "BTCCNY", 62 | "AuthenticatedAPISupport": false, 63 | "DisplayCurrency": "CNY" 64 | }, 65 | "3": { 66 | "ExchangeName": "OKCOINUSD", 67 | "Label": "OKUSD-", 68 | "APIKey": "Key", 69 | "APISecret": "Secret", 70 | "ClientID": "", 71 | "EnabledPair": "BTCUSD", 72 | "AuthenticatedAPISupport": false, 73 | "DisplayCurrency": "CNY" 74 | } 75 | }, 76 | "StrategieConfigs": { 77 | "0": { 78 | "Name": "First JS Strategy", 79 | "Description": "first strategy in javascript.", 80 | "Lang": "JS", 81 | "Code": "", 82 | "StrategyVarabiles": { 83 | "0": { 84 | "Name": "num var", 85 | "Description": "This is a number var", 86 | "Hint": "", 87 | "VarabileType": 0, 88 | "DefaultValue": 0 89 | }, 90 | "1": { 91 | "Name": "bool var", 92 | "Description": "This is a bool var", 93 | "Hint": "", 94 | "VarabileType": 1, 95 | "DefaultValue": false 96 | } 97 | }, 98 | "LocalPath": "/Users/tongxiaofeng/code/bitbot/strategy/first.js", 99 | "LocalCommandParams": "" 100 | }, 101 | "1": { 102 | "Name": "Second JS Strategy", 103 | "Description": "second strategy in javascript.", 104 | "Lang": "JS", 105 | "Code": "", 106 | "StrategyVarabiles": { 107 | "0": { 108 | "Name": "string var", 109 | "Description": "This is a string var", 110 | "Hint": "", 111 | "VarabileType": 2, 112 | "DefaultValue": "string for output." 113 | } 114 | }, 115 | "LocalPath": "/Users/tongxiaofeng/code/bitbot/strategy/second.js", 116 | "LocalCommandParams": "" 117 | } 118 | }, 119 | "BotConfigs": { 120 | "0": { 121 | "Name": "First Bot", 122 | "StrategyID": "0", 123 | "DockerID": "0", 124 | "KLineInterval": 0, 125 | "ExchangeAccountAPIIDs": "2", 126 | "Status": 1, 127 | "StatusDescription": "This robot is stopped.", 128 | "Enabled": true, 129 | "Backtesting": false, 130 | "PaperTrading": false, 131 | "StrategyVarabileValues": { 132 | "0": { 133 | "Value": 10000 134 | }, 135 | "1": { 136 | "Value": true 137 | } 138 | } 139 | }, 140 | "1": { 141 | "Name": "Second Bot", 142 | "StrategyID": "1", 143 | "DockerID": "0", 144 | "KLineInterval": 0, 145 | "ExchangeAccountAPIIDs": "0,2", 146 | "Status": 1, 147 | "StatusDescription": "This robot is stopped.", 148 | "Enabled": true, 149 | "Backtesting": false, 150 | "PaperTrading": false, 151 | "StrategyVarabileValues": { 152 | "0": { 153 | "Value": false 154 | } 155 | } 156 | } 157 | } 158 | } -------------------------------------------------------------------------------- /src/robot/strategy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type StrategyVarabileType int 4 | 5 | const ( 6 | NUMBER StrategyVarabileType = iota 7 | BOOLEAN 8 | STRING //string 9 | SELECTED 10 | ) 11 | 12 | type StrategyVarabile struct { 13 | Name string 14 | Description string 15 | Hint string 16 | VarabileType StrategyVarabileType 17 | DefaultValue interface{} 18 | } 19 | 20 | type StrategyVarabileValue struct { 21 | Value interface{} 22 | } 23 | 24 | type StrategyLang int 25 | 26 | const ( 27 | GOLANG StrategyLang = iota 28 | NODEJS 29 | PYTHON 30 | ) 31 | 32 | type StrategyConfig struct { 33 | Name string 34 | Description string 35 | Lang string //StrategyLang "JS" for javascript,"PY" for python, "GO" for Golang 36 | Code string 37 | StrategyVarabiles map[string]StrategyVarabile 38 | LocalPath string //local execute file relative path 39 | LocalCommandParams string 40 | } 41 | 42 | /* 43 | 字符串 :String 44 | 数字型 :Number 45 | 布尔型 :true或者false 46 | 选择型 :用'|'分开, 如aa|bb|cc表示列表有三个选项, 对应值为0,1,2 47 | 加密型 :String (策略参数将在本地加密后保存到服务器与秘钥保存原理一样) 48 | 49 | 参数支持定义显示条件, 比如想让a变量在b变量为1或者true的时候显示, 变量a就定义成a@b或者a@b==1 50 | 如果变量a想在变量b在选择第三个选项的时候显示, 变量a变量名就定义为a@b==3 51 | @后面是定义语法格式为 变量名+比较符+值(数字) 52 | 操作符支持==, !=, >=, <=, 变量前直接加!表示取反,比如a@!b,指b为1或者true时候,a不显示 53 | 54 | */ 55 | -------------------------------------------------------------------------------- /src/robot/test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // import ( 4 | // "fmt" 5 | // "net/http" 6 | // "reflect" 7 | // ) 8 | 9 | // func main() { 10 | // c := &http.Client{} 11 | // fooType := reflect.TypeOf(c) 12 | // for i := 0; i < fooType.NumMethod(); i++ { 13 | // method := fooType.Method(i) 14 | // fmt.Println(method.Name) 15 | // } 16 | // } 17 | 18 | // import "github.com/markcheno/go-talib" 19 | 20 | // type talib struct{ 21 | // . talib 22 | // } 23 | 24 | // func main() { 25 | // vm := otto.New() 26 | 27 | // vm.Set("talib.test", test) 28 | // vm.Run(`talib.test();`) 29 | // } 30 | 31 | // func test() { 32 | // log.Print("this is from go") 33 | // } 34 | 35 | // func main() { 36 | // var hello = func(name string) (string, error) { 37 | // return fmt.Sprintf("Hello World, %s", name), errors.New("this is my error") 38 | // } 39 | 40 | // vm := otto.New() 41 | 42 | // vm.Set("hello", hello) 43 | 44 | // _, err := vm.Run(` 45 | // name = hello("otto"); 46 | // console.log("name[0]:",name[0]); 47 | // if(typeof(name[1]) !="undefined"){ 48 | // console.log("name[1].message:",name[1].Error()); 49 | // } 50 | // `) 51 | 52 | // if err != nil { 53 | // panic(err) 54 | // } 55 | // } 56 | 57 | // package main 58 | 59 | // import ( 60 | // "fmt" 61 | // "math" 62 | 63 | // "github.com/d4l3k/talib" 64 | // ) 65 | 66 | // func main() { 67 | // fmt.Println(talib.Sin([]float64{0, math.Pi / 2})) 68 | // // => [0 1] 69 | // } 70 | -------------------------------------------------------------------------------- /src/robot/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/md5" 6 | "crypto/sha1" 7 | "crypto/sha256" 8 | "crypto/sha512" 9 | "encoding/base64" 10 | "encoding/csv" 11 | "encoding/hex" 12 | "encoding/json" 13 | "errors" 14 | "hash" 15 | "io" 16 | "io/ioutil" 17 | "log" 18 | "math" 19 | "net/http" 20 | "net/url" 21 | "os" 22 | "strconv" 23 | "strings" 24 | "time" 25 | ) 26 | 27 | const ( 28 | HASH_SHA1 = iota 29 | HASH_SHA256 30 | HASH_SHA512 31 | HASH_SHA512_384 32 | SATOSHIS_PER_BTC = 100000000 33 | SATOSHIS_PER_LTC = 100000000 34 | ) 35 | 36 | func GetMD5(input []byte) []byte { 37 | hash := md5.New() 38 | hash.Write(input) 39 | return hash.Sum(nil) 40 | } 41 | 42 | func GetSHA512(input []byte) []byte { 43 | sha := sha512.New() 44 | sha.Write(input) 45 | return sha.Sum(nil) 46 | } 47 | 48 | func GetSHA256(input []byte) []byte { 49 | sha := sha256.New() 50 | sha.Write(input) 51 | return sha.Sum(nil) 52 | } 53 | 54 | func GetHMAC(hashType int, input, key []byte) []byte { 55 | var hash func() hash.Hash 56 | 57 | switch hashType { 58 | case HASH_SHA1: 59 | { 60 | hash = sha1.New 61 | } 62 | case HASH_SHA256: 63 | { 64 | hash = sha256.New 65 | } 66 | case HASH_SHA512: 67 | { 68 | hash = sha512.New 69 | } 70 | case HASH_SHA512_384: 71 | { 72 | hash = sha512.New384 73 | } 74 | } 75 | 76 | hmac := hmac.New(hash, []byte(key)) 77 | hmac.Write(input) 78 | return hmac.Sum(nil) 79 | } 80 | 81 | func HexEncodeToString(input []byte) string { 82 | return hex.EncodeToString(input) 83 | } 84 | 85 | func Base64Decode(input string) ([]byte, error) { 86 | result, err := base64.StdEncoding.DecodeString(input) 87 | if err != nil { 88 | return nil, err 89 | } 90 | return result, nil 91 | } 92 | 93 | func Base64Encode(input []byte) string { 94 | return base64.StdEncoding.EncodeToString(input) 95 | } 96 | 97 | func StringSliceDifference(slice1 []string, slice2 []string) []string { 98 | var diff []string 99 | for i := 0; i < 2; i++ { 100 | for _, s1 := range slice1 { 101 | found := false 102 | for _, s2 := range slice2 { 103 | if s1 == s2 { 104 | found = true 105 | break 106 | } 107 | } 108 | if !found { 109 | diff = append(diff, s1) 110 | } 111 | } 112 | if i == 0 { 113 | slice1, slice2 = slice2, slice1 114 | } 115 | } 116 | return diff 117 | } 118 | 119 | func StringContains(input, substring string) bool { 120 | return strings.Contains(input, substring) 121 | } 122 | 123 | func JoinStrings(input []string, seperator string) string { 124 | return strings.Join(input, seperator) 125 | } 126 | 127 | func SplitStrings(input, seperator string) []string { 128 | return strings.Split(input, seperator) 129 | } 130 | 131 | func TrimString(input, cutset string) string { 132 | return strings.Trim(input, cutset) 133 | } 134 | 135 | func StringToUpper(input string) string { 136 | return strings.ToUpper(input) 137 | } 138 | 139 | func StringToLower(input string) string { 140 | return strings.ToLower(input) 141 | } 142 | 143 | func RoundFloat(x float64, prec int) float64 { 144 | var rounder float64 145 | pow := math.Pow(10, float64(prec)) 146 | intermed := x * pow 147 | _, frac := math.Modf(intermed) 148 | intermed += .5 149 | x = .5 150 | if frac < 0.0 { 151 | x = -.5 152 | intermed -= 1 153 | } 154 | if frac >= x { 155 | rounder = math.Ceil(intermed) 156 | } else { 157 | rounder = math.Floor(intermed) 158 | } 159 | 160 | return rounder / pow 161 | } 162 | 163 | func IsEnabled(isEnabled bool) string { 164 | if isEnabled { 165 | return "Enabled" 166 | } else { 167 | return "Disabled" 168 | } 169 | } 170 | 171 | func CalculateAmountWithFee(amount, fee float64) float64 { 172 | return amount + CalculateFee(amount, fee) 173 | } 174 | 175 | func CalculateFee(amount, fee float64) float64 { 176 | return amount * (fee / 100) 177 | } 178 | 179 | func CalculatePercentageDifference(amount, secondAmount float64) float64 { 180 | return (secondAmount - amount) / amount * 100 181 | } 182 | 183 | func CalculateNetProfit(amount, priceThen, priceNow, costs float64) float64 { 184 | return (priceNow * amount) - (priceThen * amount) - costs 185 | } 186 | 187 | func SendHTTPRequest(method, path string, headers map[string]string, body io.Reader) (string, error) { 188 | result := strings.ToUpper(method) 189 | 190 | if result != "POST" && result != "GET" && result != "DELETE" { 191 | return "", errors.New("Invalid HTTP method specified.") 192 | } 193 | 194 | req, err := http.NewRequest(method, path, body) 195 | 196 | if err != nil { 197 | return "", err 198 | } 199 | 200 | for k, v := range headers { 201 | req.Header.Add(k, v) 202 | } 203 | 204 | httpClient := &http.Client{} 205 | resp, err := httpClient.Do(req) 206 | 207 | if err != nil { 208 | return "", err 209 | } 210 | 211 | contents, err := ioutil.ReadAll(resp.Body) 212 | defer resp.Body.Close() 213 | 214 | if err != nil { 215 | return "", err 216 | } 217 | 218 | return string(contents), nil 219 | } 220 | 221 | func SendHTTPGetRequest(url string, jsonDecode bool, result interface{}) (err error) { 222 | res, err := http.Get(url) 223 | 224 | if err != nil { 225 | return err 226 | } 227 | 228 | if res.StatusCode != 200 { 229 | log.Printf("URL: %s:\n", url) 230 | log.Printf("HTTP status code: %d\n", res.StatusCode) 231 | return errors.New("Status code was not 200.") 232 | } 233 | 234 | contents, err := ioutil.ReadAll(res.Body) 235 | 236 | if err != nil { 237 | return err 238 | } 239 | 240 | defer res.Body.Close() 241 | 242 | if jsonDecode { 243 | err := JSONDecode(contents, &result) 244 | 245 | if err != nil { 246 | return err 247 | } 248 | } else { 249 | result = &contents 250 | } 251 | 252 | return nil 253 | } 254 | 255 | func JSONEncode(v interface{}) ([]byte, error) { 256 | json, err := json.Marshal(&v) 257 | 258 | if err != nil { 259 | return nil, err 260 | } 261 | 262 | return json, nil 263 | } 264 | 265 | func JSONDecode(data []byte, to interface{}) error { 266 | err := json.Unmarshal(data, &to) 267 | 268 | if err != nil { 269 | return err 270 | } 271 | 272 | return nil 273 | } 274 | 275 | func EncodeURLValues(url string, values url.Values) string { 276 | path := url 277 | if len(values) > 0 { 278 | path += "?" + values.Encode() 279 | } 280 | return path 281 | } 282 | 283 | func ExtractHost(address string) string { 284 | host := SplitStrings(address, ":")[0] 285 | if host == "" { 286 | return "localhost" 287 | } 288 | return host 289 | } 290 | 291 | func ExtractPort(host string) int { 292 | portStr := SplitStrings(host, ":")[1] 293 | port, _ := strconv.Atoi(portStr) 294 | return port 295 | } 296 | 297 | func OutputCSV(path string, data [][]string) error { 298 | file, err := os.Create(path) 299 | if err != nil { 300 | return err 301 | } 302 | 303 | writer := csv.NewWriter(file) 304 | 305 | err = writer.WriteAll(data) 306 | if err != nil { 307 | return err 308 | } 309 | 310 | defer writer.Flush() 311 | return nil 312 | } 313 | 314 | type Marshaler interface { 315 | MarshalJSON() ([]byte, error) 316 | } 317 | 318 | type JSONTime time.Time 319 | 320 | // const timeLayout = "2006/01/02|15:04:05" 321 | 322 | // func (t JSONTime) MarshalJSON() ([]byte, error) { 323 | // stamp := fmt.Sprintf("\"%s\"", time.Time(t).Format(timeLayout)) 324 | // return []byte(stamp), nil 325 | // } 326 | 327 | // func (t JSONTime) UnmarshalJSON(b []byte) (err error) { 328 | // s := strings.Trim(string(b), "\"") 329 | // if s == "null" { 330 | // ct.Time = time.Time{} 331 | // return 332 | // } 333 | // ct.Time, err = time.Parse(ctLayout, s) 334 | // return 335 | // } 336 | -------------------------------------------------------------------------------- /src/robot/utils_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestIsEnabled(t *testing.T) { 11 | t.Parallel() 12 | expected := "Enabled" 13 | actual := IsEnabled(true) 14 | if actual != expected { 15 | t.Error(fmt.Sprintf("Test failed. Expected %s. Actual %s", expected, actual)) 16 | } 17 | 18 | expected = "Disabled" 19 | actual = IsEnabled(false) 20 | if actual != expected { 21 | t.Error(fmt.Sprintf("Test failed. Expected %s. Actual %s", expected, actual)) 22 | } 23 | } 24 | 25 | func TestGetMD5(t *testing.T) { 26 | t.Parallel() 27 | var originalString = []byte("I am testing the MD5 function in common!") 28 | var expectedOutput = []byte("18fddf4a41ba90a7352765e62e7a8744") 29 | actualOutput := GetMD5(originalString) 30 | actualStr := HexEncodeToString(actualOutput) 31 | if !bytes.Equal(expectedOutput, []byte(actualStr)) { 32 | t.Error(fmt.Sprintf("Test failed. Expected '%s'. Actual '%s'", expectedOutput, []byte(actualStr))) 33 | } 34 | 35 | } 36 | 37 | func TestGetSHA512(t *testing.T) { 38 | t.Parallel() 39 | var originalString = []byte("I am testing the GetSHA512 function in common!") 40 | var expectedOutput = []byte("a2273f492ea73fddc4f25c267b34b3b74998bd8a6301149e1e1c835678e3c0b90859fce22e4e7af33bde1711cbb924809aedf5d759d648d61774b7185c5dc02b") 41 | actualOutput := GetSHA512(originalString) 42 | actualStr := HexEncodeToString(actualOutput) 43 | if !bytes.Equal(expectedOutput, []byte(actualStr)) { 44 | t.Error(fmt.Sprintf("Test failed. Expected '%x'. Actual '%x'", expectedOutput, []byte(actualStr))) 45 | } 46 | } 47 | 48 | func TestGetSHA256(t *testing.T) { 49 | t.Parallel() 50 | var originalString = []byte("I am testing the GetSHA256 function in common!") 51 | var expectedOutput = []byte("0962813d7a9f739cdcb7f0c0be0c2a13bd630167e6e54468266e4af6b1ad9303") 52 | actualOutput := GetSHA256(originalString) 53 | actualStr := HexEncodeToString(actualOutput) 54 | if !bytes.Equal(expectedOutput, []byte(actualStr)) { 55 | t.Error(fmt.Sprintf("Test failed. Expected '%x'. Actual '%x'", expectedOutput, []byte(actualStr))) 56 | } 57 | } 58 | 59 | func TestStringToLower(t *testing.T) { 60 | t.Parallel() 61 | upperCaseString := "HEY MAN" 62 | expectedResult := "hey man" 63 | actualResult := StringToLower(upperCaseString) 64 | if actualResult != expectedResult { 65 | t.Error("...") 66 | } 67 | } 68 | 69 | func TestStringToUpper(t *testing.T) { 70 | t.Parallel() 71 | upperCaseString := "hey man" 72 | expectedResult := "HEY MAN" 73 | actualResult := StringToUpper(upperCaseString) 74 | if actualResult != expectedResult { 75 | t.Error("...") 76 | } 77 | } 78 | 79 | func TestHexEncodeToString(t *testing.T) { 80 | t.Parallel() 81 | originalInput := []byte("string") 82 | expectedOutput := "737472696e67" 83 | actualResult := HexEncodeToString(originalInput) 84 | if actualResult != expectedOutput { 85 | t.Error(fmt.Sprintf("Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult)) 86 | } 87 | } 88 | 89 | func TestBase64Decode(t *testing.T) { 90 | t.Parallel() 91 | originalInput := "aGVsbG8=" 92 | expectedOutput := []byte("hello") 93 | actualResult, err := Base64Decode(originalInput) 94 | if !bytes.Equal(actualResult, expectedOutput) { 95 | t.Error(fmt.Sprintf("Test failed. Expected '%s'. Actual '%s'. Error: %s", expectedOutput, actualResult, err)) 96 | } 97 | } 98 | 99 | func TestBase64Encode(t *testing.T) { 100 | t.Parallel() 101 | originalInput := []byte("hello") 102 | expectedOutput := "aGVsbG8=" 103 | actualResult := Base64Encode(originalInput) 104 | if actualResult != expectedOutput { 105 | t.Error(fmt.Sprintf("Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult)) 106 | } 107 | } 108 | 109 | func TestStringSliceDifference(t *testing.T) { 110 | t.Parallel() 111 | originalInputOne := []string{"hello"} 112 | originalInputTwo := []string{"moto"} 113 | expectedOutput := []string{"hello moto"} 114 | actualResult := StringSliceDifference(originalInputOne, originalInputTwo) 115 | if reflect.DeepEqual(expectedOutput, actualResult) { 116 | t.Error(fmt.Sprintf("Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult)) 117 | } 118 | } 119 | 120 | func TestStringContains(t *testing.T) { 121 | t.Parallel() 122 | originalInput := "hello" 123 | originalInputSubstring := "he" 124 | expectedOutput := true 125 | actualResult := StringContains(originalInput, originalInputSubstring) 126 | if actualResult != expectedOutput { 127 | t.Error(fmt.Sprintf("Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult)) 128 | } 129 | } 130 | 131 | func TestJoinStrings(t *testing.T) { 132 | t.Parallel() 133 | originalInputOne := []string{"hello", "moto"} 134 | seperator := "," 135 | expectedOutput := "hello,moto" 136 | actualResult := JoinStrings(originalInputOne, seperator) 137 | if expectedOutput != actualResult { 138 | t.Error(fmt.Sprintf("Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult)) 139 | } 140 | } 141 | 142 | func TestSplitStrings(t *testing.T) { 143 | t.Parallel() 144 | originalInputOne := "hello,moto" 145 | seperator := "," 146 | expectedOutput := []string{"hello", "moto"} 147 | actualResult := SplitStrings(originalInputOne, seperator) 148 | if !reflect.DeepEqual(expectedOutput, actualResult) { 149 | t.Error(fmt.Sprintf("Test failed. Expected '%s'. Actual '%s'", expectedOutput, actualResult)) 150 | } 151 | } 152 | 153 | func TestRoundFloat(t *testing.T) { 154 | t.Parallel() 155 | originalInput := float64(1.4545445445) 156 | precisionInput := 2 157 | expectedOutput := float64(1.45) 158 | actualResult := RoundFloat(originalInput, precisionInput) 159 | if expectedOutput != actualResult { 160 | t.Error(fmt.Sprintf("Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult)) 161 | } 162 | } 163 | 164 | func TestCalculateFee(t *testing.T) { 165 | t.Parallel() 166 | originalInput := float64(1) 167 | fee := float64(1) 168 | expectedOutput := float64(0.01) 169 | actualResult := CalculateFee(originalInput, fee) 170 | if expectedOutput != actualResult { 171 | t.Error(fmt.Sprintf("Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult)) 172 | } 173 | } 174 | 175 | func TestCalculateAmountWithFee(t *testing.T) { 176 | t.Parallel() 177 | originalInput := float64(1) 178 | fee := float64(1) 179 | expectedOutput := float64(1.01) 180 | actualResult := CalculateAmountWithFee(originalInput, fee) 181 | if expectedOutput != actualResult { 182 | t.Error(fmt.Sprintf("Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult)) 183 | } 184 | } 185 | 186 | func TestCalculatePercentageDifference(t *testing.T) { 187 | t.Parallel() 188 | originalInput := float64(5) 189 | secondAmount := float64(10) 190 | expectedOutput := float64(100) 191 | actualResult := CalculatePercentageDifference(originalInput, secondAmount) 192 | if expectedOutput != actualResult { 193 | t.Error(fmt.Sprintf("Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult)) 194 | } 195 | } 196 | 197 | func TestCalculateNetProfit(t *testing.T) { 198 | t.Parallel() 199 | amount := float64(5) 200 | priceThen := float64(1) 201 | priceNow := float64(10) 202 | costs := float64(1) 203 | expectedOutput := float64(44) 204 | actualResult := CalculateNetProfit(amount, priceThen, priceNow, costs) 205 | if expectedOutput != actualResult { 206 | t.Error(fmt.Sprintf("Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult)) 207 | } 208 | } 209 | 210 | func TestExtractHost(t *testing.T) { 211 | t.Parallel() 212 | address := "localhost:1337" 213 | expectedOutput := "localhost" 214 | actualResult := ExtractHost(address) 215 | if expectedOutput != actualResult { 216 | t.Error(fmt.Sprintf("Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult)) 217 | } 218 | 219 | address = "192.168.1.100:1337" 220 | expectedOutput = "192.168.1.100" 221 | actualResult = ExtractHost(address) 222 | if expectedOutput != actualResult { 223 | t.Error(fmt.Sprintf("Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult)) 224 | } 225 | } 226 | 227 | func TestExtractPort(t *testing.T) { 228 | t.Parallel() 229 | address := "localhost:1337" 230 | expectedOutput := 1337 231 | actualResult := ExtractPort(address) 232 | if expectedOutput != actualResult { 233 | t.Error(fmt.Sprintf("Test failed. Expected '%f'. Actual '%f'.", expectedOutput, actualResult)) 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /strategy/first.js: -------------------------------------------------------------------------------- 1 | function log(string){ 2 | console.log(string) 3 | } 4 | 5 | 6 | log("from global of first context") 7 | function onTick() { 8 | // 策略采用轮询而非事件驱动是因为作者喜欢对代码100%的掌控力. 9 | } 10 | 11 | function main() { 12 | // console.log("Working in First JS Strategy."); 13 | // for (i = 0; i < exchanges.length; i++) { 14 | // log("Exchange Name:",exchanges[0].GetName()); 15 | // log("Acct:" ,exchange.GetEnabledCurrencies()) 16 | // } 17 | log(exchange.GetName()) 18 | 19 | var ticker = exchange.GetTicker() 20 | log(ticker.High) 21 | 22 | 23 | // for(i = 0;i<100;i++){ 24 | // console.log("------- :" + i); 25 | // } 26 | 27 | var moneys = exchange.GetEnabledPair(); 28 | console.log(moneys) 29 | 30 | 31 | console.log("Finish main First JS Strategy."); 32 | } 33 | 34 | function onExit(){ 35 | log("Exiting first strategy.") 36 | } 37 | 38 | function onError(){ 39 | 40 | } -------------------------------------------------------------------------------- /strategy/second.js: -------------------------------------------------------------------------------- 1 | function log(string){ 2 | console.log(string) 3 | } 4 | 5 | function main() { 6 | log("Running second strategy") 7 | // var before = Date.now(); 8 | 9 | // for(i = 0;i<100;i++){ 10 | // console.log("+++++++ :" + i); 11 | // } 12 | // var after = Date.now(); 13 | 14 | //var diff = after - before; 15 | //log("Time used:" + diff + " ms") 16 | 17 | var version = Version(); 18 | 19 | log("Docker Version:"); 20 | log(version); 21 | // log("Second strategy started sleeping"); 22 | // var before = Date.now(); 23 | // Sleep(3000); 24 | // var after = Date.now(); 25 | 26 | // var diff = after - before; 27 | // log("Time slept:" + diff + " ms") 28 | 29 | //_.each([1, 2, 3,4,5,6,7], log); 30 | Log("this is a test Log in second strategy") 31 | //Log("Sin:", Sin([1.5,2.0])) 32 | log("finish main in second strategy") 33 | } 34 | 35 | 36 | function onTick() { 37 | // 策略采用轮询而非事件驱动是因为作者喜欢对代码100%的掌控力. 38 | } 39 | 40 | function onExit(){ 41 | log("exiting second strategy from js"); 42 | } 43 | 44 | function onError(){ 45 | 46 | } --------------------------------------------------------------------------------