Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ogxing committed Jun 9, 2018
0 parents commit fe0a675
Show file tree
Hide file tree
Showing 33 changed files with 2,426 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
hs_err_pid*.log
pom.xml
pom.xml.asc
**/gen
**/target
.*
!.gitignore
1 change: 1 addition & 0 deletions boot.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BOOT_CLOJURE_VERSION=1.9.0
9 changes: 9 additions & 0 deletions build.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@ECHO OFF
IF EXIST ".\build\" rd /q /s ".\build"
MKDIR build\db
MKDIR build\target\public
.\boot.exe build-cljs
MOVE .\target\public\main.js .\build\target\public\
MOVE .\target\public\index.html .\build\target\public\
.\boot.exe build-server
MOVE .\target\* .\build\
70 changes: 70 additions & 0 deletions build.boot
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
(set-env!
:resource-paths #{"resources"}
:source-paths #{"src/clj" "src/cljs"}
:dependencies '[[adzerk/boot-cljs "2.1.4" :scope "test"]
[adzerk/boot-reload "0.5.2" :scope "test"]
[javax.xml.bind/jaxb-api "2.3.0"] ; necessary for Java 9 compatibility
; Clojure dependencies. (server)
[org.clojure/clojure "1.9.0"]
[clj-http "3.9.0"]
[clj-time "0.14.3"]
[com.draines/postal "2.0.2"]
[org.clojure/data.json "0.2.6"]
[org.clojure/java.jdbc "0.7.6"]
[org.xerial/sqlite-jdbc "3.20.1"]
[ring "1.6.3"]
[compojure "1.6.1"]
[http-kit "2.2.0"]
[crypto-random "1.2.0"]
[org.clojure/data.codec "0.1.1"]
[org.clojure/tools.nrepl "0.2.12"]
[samestep/boot-refresh "0.1.0" :scope "test"]
; ClojureScript dependencies. (client)
[org.clojure/clojurescript "1.10.238" :scope "test"]
[cljs-ajax "0.7.3" :scope "test"]
[reagent "0.7.0" :scope "test"]
[cljsjs/react-dom "16.3.2-0" :scope "test"]
[cljsjs/react "16.3.2-0" :scope "test"]
[soda-ash "0.79.1" :scope "test"]])
(task-options!
pom {:project 'trexd
:version "1.0.0-SNAPSHOT"
:description "BitTrex Daemon"}
aot {:namespace #{'trexd.core}}
jar {:main 'trexd.core})

(require
'[adzerk.boot-cljs :refer [cljs]]
'[adzerk.boot-reload :refer [reload]]
'[samestep.boot-refresh :refer [refresh]]
'trexd.core)

(deftask run []
(comp
(watch)
(reload :asset-path "public")
(cljs
:source-map true
:optimizations :none
:compiler-options {:asset-path "main.out"})
(target)
(with-pass-thru _
(trexd.core/-main))))

(deftask build-server []
(comp
(aot)
(pom)
(uber)
(jar)
(sift :include #{#"\.jar$"})
(target)))

(deftask build-cljs []
(comp
(cljs
:optimizations :advanced
:compiler-options {:asset-path "main.out"}
:pretty-print true
:pseudo-names true)
(target)))
8 changes: 8 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
rm -rf ./build
mkdir build/db
mkdir build/target/public
boot build-cljs
mv ./target/public/main.js ./build/target/public/
mv ./target/public/index.html ./build/target/public/
boot build-server
mv ./target/* ./build/
28 changes: 28 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## Bittrex Trading Daemon (trexd)

### Features:

- Setup email alert for:
- When price reaches ? rate.
- When limit buy/sell order is completed.
- View, create and cancel orders.
- Simple web based interface.
- Fully scriptable (Uses clojure REPL)
- All services run on your machine, no hidden third party.
- And more to come..?



### Setup:

Download then extract latest release.

In cmd / shell:

cd trexd

java -jar trexd-*.jar

Follow setup steps given by the program and it should setup a working webserver in no time.

GUI is hosted locally on localhost:8080
18 changes: 18 additions & 0 deletions resources/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<! Choose between local or remote copy. Must be 2.2.x, 2.3.x has bug in modal screen display./>
<! link rel="stylesheet" href="./semantic.min.css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.14/semantic.min.css">
</head>

<body>
<! Style makes div display full whitespace instead of default collapsed whitespace. />
<style>div {white-space: pre; font-family: "Courier New", monospace;}</style>
<div id="menu"></div>
<div id="content"></div>
<div id="repl"></div>
<script src="main.js"></script>
</body>
</html>
3 changes: 3 additions & 0 deletions resources/public/main.cljs.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{:require [trexd.core]
:init-fns []
:compiler-options {}}
364 changes: 364 additions & 0 deletions resources/public/semantic.min.css

Large diffs are not rendered by default.

45 changes: 45 additions & 0 deletions src/clj/trexd/aes.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
(ns trexd.aes
(:refer-clojure :exclude [bytes])
(:gen-class))

;; Source: https://stackoverflow.com/questions/10221257/is-there-an-aes-library-for-clojure
;; Adapted to convert all input to string.
(import (javax.crypto Cipher KeyGenerator SecretKey)
(javax.crypto.spec SecretKeySpec)
(java.security SecureRandom)
(org.apache.commons.codec.binary Base64))

(defn bytes [s]
(.getBytes s "UTF-8"))

(defn base64 [b]
(Base64/encodeBase64String b))

(defn debase64 [s]
(Base64/decodeBase64 (bytes s)))

(defn get-raw-key [seed]
(let [keygen (KeyGenerator/getInstance "AES")
sr (SecureRandom/getInstance "SHA1PRNG")]
(.setSeed sr (bytes seed))
(.init keygen 128 sr)
(.. keygen generateKey getEncoded)))

(defn get-cipher [mode seed]
(let [key-spec (SecretKeySpec. (get-raw-key seed) "AES")
cipher (Cipher/getInstance "AES")]
(.init cipher mode key-spec)
cipher))

(defn encrypt [text key]
(let [text-str (str text)
key-str (str key)]
(let [bytes (bytes text-str)
cipher (get-cipher Cipher/ENCRYPT_MODE key-str)]
(base64 (.doFinal cipher bytes)))))

(defn decrypt [text key]
(let [text-str (str text)
key-str (str key)]
(let [cipher (get-cipher Cipher/DECRYPT_MODE key-str)]
(String. (.doFinal cipher (debase64 text-str))))))
177 changes: 177 additions & 0 deletions src/clj/trexd/bittrex_api.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
(ns trexd.bittrex-api
(:require [trexd.util :as u])
(:gen-class))

;; Final Format: {:success true, :msg "oh cow", :command "get-market", :result data}
(defn r-template
"Bittrex result map template. All exchanges share the same output format."
[original-map command result]
{:success (:success original-map)
:msg (if (nil? (:msg original-map))(:message original-map)(:msg original-map))
:command command
:result result})

;; Bittrex Wrapper.
;; Converts to predefined clojure map template before returning. (json -> map -> templated map format)
;; Some of the result fields are not recorded or renamed. eg timestamp, null fields.
;; https://support.bittrex.com/hc/en-us/articles/115003723911-Developer-s-Guide-API
;; Public API
(defn getmarkets []
(u/post->map "https://bittrex.com/api/v1.1/public/getmarkets"))
(defn getcurrencies []
(u/post->map "https://bittrex.com/api/v1.1/public/getcurrencies"))
(defn getticker [market]
(let [r (u/post->map "https://bittrex.com/api/v1.1/public/getticker" {:query-params {"market" market}})
rr (:result r)]
(r-template r "get-ticker" {:bid (rr :Bid)
:ask (rr :Ask)
:last (rr :Last)})))
(defn getmarketsummaries []
(let [r (u/post->map "https://bittrex.com/api/v1.1/public/getmarketsummaries")]
(r-template r "get-market-summaries" (for [rr (:result r)]
{:pair (rr :MarketName)
:high (rr :High)
:low (rr :Low)
:last (rr :Last)
:volume (rr :Volume)
:base (rr :BaseVolume)
:bid (rr :Bid)
:ask (rr :Ask)}))))
(defn getmarketsummary [market]
(u/post->map "https://bittrex.com/api/v1.1/public/getmarketsummary" {:query-params {"market" market}}))
(defn getorderbook
([market]
(let [r (u/post->map "https://bittrex.com/api/v1.1/public/getorderbook" {:query-params {"market" market "type" "both"}})
rr (:result r)]
(r-template r "get-orderbook" {:buy (for [rrb (:buy rr)]
{:size (rrb :Quantity)
:rate (rrb :Rate)})
:sell (for [rrs (:sell rr)]
{:size (rrs :Quantity)
:rate (rrs :Rate)})})))
([market type]
(u/post->map "https://bittrex.com/api/v1.1/public/getorderbook" {:query-params {"market" market "type" type}})))

(defn getmarkethistory [market]
(let [r (u/post->map "https://bittrex.com/api/v1.1/public/getmarkethistory" {:query-params {"market" market}})]
(r-template r "get-market-history") (for [rr (:result r)]
{:id (rr :Id)
:size (rr :Quantity)
:rate (rr :Price)
:total (rr :Total)
:type (rr :OrderType)
:timestamp (u/date->epoch (rr :TimeStamp))})))

;; Market API
(defn buylimit [market quantity rate]
(let [r (u/post->map-signed "https://bittrex.com/api/v1.1/market/buylimit" {:query-params {"market" market "quantity" quantity "rate" rate}})
rr (:result r)]
(r-template r "create-limit-buy" {:uuid (rr :uuid)})))

(defn selllimit [market quantity rate]
(let [r (u/post->map-signed "https://bittrex.com/api/v1.1/market/selllimit" {:query-params {"market" market "quantity" quantity "rate" rate}})
rr (:result r)]
(r-template r "create-limit-sell" {:uuid (rr :uuid)})))

(defn cancel [uuid]
(let [r (u/post->map-signed "https://bittrex.com/api/v1.1/market/cancel" {:query-params {"uuid" uuid}})]
(r-template r "cancel-order" {})))

(defn getopenorders
([]
(let [r (u/post->map-signed "https://bittrex.com/api/v1.1/market/getopenorders")]
(r-template r "get-open-orders" (for [rr (:result r)]
{:uuid (rr :OrderUuid)
:pair (rr :Exchange)
:type (rr :OrderType)
:size (rr :Quantity)
:left (rr :QuantityRemaining)
:rate (rr :Limit)
:timestamp (u/date->epoch (rr :Opened))}))))
([market]
(u/post->map-signed "https://bittrex.com/api/v1.1/market/getopenorders" {:query-params {"market" market}})))

;; Account API
(defn getbalances []
(let [r (u/post->map-signed "https://bittrex.com/api/v1.1/account/getbalances")]
(r-template r "get-balances" (for [rr (:result r)]
{:pair (rr :Currency)
:balance (rr :Balance)
:available (rr :Available)
:pending (rr :Pending)}))))

(defn getbalance [currency]
(let [r (u/post->map-signed "https://bittrex.com/api/v1.1/account/getbalance" {:query-params {"currency" currency}})
rr (:result r)]
(r-template r "get-balances" {:pair (rr :Currency)
:balance (rr :Balance)
:available (rr :Available)
:pending (rr :Pending)})))

(defn getdepositaddress [currency]
(let [r (u/post->map-signed "https://bittrex.com/api/v1.1/account/getdepositaddress" {:query-params {"currency" currency}})
rr (:result r)]
(r-template r "get-deposit-address" {:pair (rr :Currency)
:address (rr :Address)})))

(defn withdraw
([currency quantity address]
(let [r (u/post->map-signed "https://bittrex.com/api/v1.1/account/withdraw" {:query-params {"currency" currency "quantity" quantity "address" address}})
rr (:result r)]
(r-template r "withdraw" {:uuid (rr :uuid)})))
([currency quantity address paymentid]
(let [r (u/post->map-signed "https://bittrex.com/api/v1.1/account/withdraw" {:query-params {"currency" currency "quantity" quantity "address" address "paymentid" paymentid}})
rr (:result r)]
(r-template r "withdraw" {:uuid (rr :uuid)}))))

(defn getorder [uuid]
(let [r (u/post->map-signed "https://bittrex.com/api/v1.1/account/getorder" {:query-params {"uuid" uuid}})
rr (:result r)]
(r-template r "get-order" {:uuid (rr :OrderUuid)
:pair (rr :Exchange)
:type (rr :Type)
:size (rr :Quantity)
:left (rr :QuantityRemaining)
:rate (rr :Limit)
:timestamp (u/date->epoch (rr :Opened))
:isopen (rr :IsOpen)})))

(defn getorderhistory
([]
(let [r (u/post->map-signed "https://bittrex.com/api/v1.1/account/getorderhistory")]
(r-template r "get-order-history" (for [rr (:result r)]
{:uuid (rr :uuid)
:pair (rr :Exchange)
:type (rr :Type)
:rate (rr :Limit)
:size (rr :Quantity)
:left (rr :QuantityRemaining)
:timestamp (u/date->epoch (rr :TimeStamp))}))))
([market]
(u/post->map-signed "https://bittrex.com/api/v1.1/account/getorderhistory" {:query-params {"market" market}})))

(defn getwithdrawalhistory
([]
(let [r (u/post->map-signed "https://bittrex.com/api/v1.1/account/getwithdrawalhistory")]
(r-template r "get-withdrawal-history" (for [rr (:result r)]
{:uuid (rr :uuid)
:pair (rr :Currency)
:size (rr :Amount)
:address (rr :Address)
:txcost (rr :TxCost)
:timestamp (u/date->epoch (rr :TimeStamp))}))))
([currency]
(u/post->map-signed "https://bittrex.com/api/v1.1/account/getwithdrawalhistory" {:query-params {"currency" currency}})))

(defn getdeposithistory
([]
(let [r (u/post->map-signed "https://bittrex.com/api/v1.1/account/getdeposithistory")]
(r-template r "get-deposit-history" (for [rr (:result r)]
{:uuid (rr :uuid)
:pair (rr :Currency)
:size (rr :Amount)
:address (rr :Address)
:txcost (rr :TxCost)
:timestamp (u/date->epoch (rr :TimeStamp))}))))
([currency]
(u/post->map-signed "https://bittrex.com/api/v1.1/account/getdeposithistory" {:query-params {"currency" currency}})))
Loading

0 comments on commit fe0a675

Please sign in to comment.