├── docker
└── redis
│ └── Dockerfile
├── example.server.config.json
├── src
├── clj
│ ├── copy_trader
│ │ ├── console.clj
│ │ ├── core.clj
│ │ ├── config.clj
│ │ ├── util.clj
│ │ ├── websocket
│ │ │ ├── event.clj
│ │ │ ├── security.clj
│ │ │ ├── client.clj
│ │ │ └── server.clj
│ │ ├── scheduling
│ │ │ ├── websocket_keepalive_job.clj
│ │ │ ├── jobs.clj
│ │ │ ├── trader_balances_job.clj
│ │ │ └── scheduler.clj
│ │ ├── system.clj
│ │ ├── exchange
│ │ │ ├── traders.clj
│ │ │ ├── alpaca
│ │ │ │ ├── driver.clj
│ │ │ │ └── trader.clj
│ │ │ ├── trader.clj
│ │ │ └── ameritrade
│ │ │ │ ├── driver.clj
│ │ │ │ └── trader.clj
│ │ ├── server.clj
│ │ └── cache.clj
│ └── log4j.properties
├── prod
│ └── user.clj
└── dev
│ └── user.clj
├── .gitignore
├── .dir-locals.el
├── Dockerfile
├── docker-compose.yml
├── Makefile
├── example.client.config.json
├── deps.edn
├── doc
└── Ameritrade.md
├── README.md
└── LICENSE
/docker/redis/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM redis:6.2.3
2 |
3 | VOLUME /data
--------------------------------------------------------------------------------
/example.server.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "servers": [
3 | ],
4 | "traders": [
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/console.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.console
2 | (:require
3 | [copy-trader.system :as system]))
4 |
5 | (def halt system/halt)
6 | (def go system/go)
7 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/core.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.core)
2 |
3 | (defonce state (atom {:is-running? false}))
4 |
5 | (defn is-running?
6 | []
7 | (:is-running? @state))
8 |
--------------------------------------------------------------------------------
/src/prod/user.clj:
--------------------------------------------------------------------------------
1 | (ns user
2 | (:require copy-trader.console))
3 |
4 | (defn dev
5 | []
6 | (in-ns 'copy-trader.console)
7 | (eval '(go)))
8 |
9 | (dev)
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .cpcache
2 | .redis_root
3 | .nrepl-port
4 | .DS_STORE
5 | .tags
6 | *.swp
7 | *.swo
8 | *.class
9 | *.jar
10 | .#*
11 | log
12 |
13 | config.json
14 | public-key.pem
15 | private-key.pem
16 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/config.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.config
2 | (:require
3 | [cheshire.core :refer [parse-string]]
4 | [clojure.java.io :as io]))
5 |
6 | (defn config
7 | []
8 | (-> (io/file "config.json")
9 | slurp
10 | (parse-string true)))
11 |
--------------------------------------------------------------------------------
/src/dev/user.clj:
--------------------------------------------------------------------------------
1 | (ns user)
2 |
3 | (defn dev
4 | []
5 | (println "Enter (go) to start"))
6 |
7 | (defn go
8 | []
9 | (require 'copy-trader.console)
10 | (in-ns 'copy-trader.console)
11 | (eval '(go))
12 | (println "Running. Use (halt) to stop."))
13 |
14 | (dev)
15 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/util.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.util
2 | (:import [clojure.lang Atom]))
3 |
4 | (defn atom?
5 | [x]
6 | (instance? Atom x))
7 |
8 | (defn to-precision
9 | [n precision]
10 | (let [multiplier (Math/pow 10 precision)]
11 | (/ (Math/floor (* n multiplier))
12 | multiplier)))
13 |
--------------------------------------------------------------------------------
/.dir-locals.el:
--------------------------------------------------------------------------------
1 | ((nil . ((cider-preferred-build-tool . clojure-cli)
2 | (cider-clojure-cli-global-options . "-A:dev")
3 | (cider-repl-init-code . ("(dev)"))
4 |
5 | (cider-repl-display-help-banner . nil)
6 | (cider-redirect-server-output-to-repl . t)
7 | (clojure-toplevel-inside-comment-form . t))))
8 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/websocket/event.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.websocket.event)
2 |
3 | (defmulti on-event
4 | (fn [_uri {:keys [message-code _payload]}]
5 | message-code))
6 |
7 | (defmethod on-event :default
8 | [_uri _msg]
9 | :not-implemented)
10 |
11 | ;; TODO -- implement too-many-clients error handler
12 |
13 | (def ONE-DAY
14 | (* 1000 60 60 24))
15 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM openjdk:11.0.13
2 |
3 | # Recommended setting for Crux
4 | ENV MALLOC_ARENA_MAX=2
5 |
6 | RUN apt-get update && \
7 | apt install -y build-essential rlwrap
8 |
9 | # Pull and prepare Clojure
10 | RUN curl -O https://download.clojure.org/install/linux-install-1.10.3.822.sh
11 | RUN chmod +x linux-install-1.10.3.822.sh
12 | RUN ./linux-install-1.10.3.822.sh
13 |
14 | EXPOSE 51585
15 | EXPOSE 40404
16 | WORKDIR /usr/src/app
17 |
18 | ENTRYPOINT ["make", "run"]
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.8"
2 | services:
3 | copy-trader-redis:
4 | image:
5 | copy-trader-redis
6 | expose:
7 | - "6379"
8 | volumes:
9 | - ./.redis_root:/data
10 |
11 | copy-trader:
12 | image:
13 | copy-trader
14 | depends_on:
15 | - copy-trader-redis
16 | environment:
17 | - REDIS_ENDPOINT=redis://copy-trader-redis:6379
18 | ports:
19 | - "40404:40404"
20 | - "51585:51585"
21 | volumes:
22 | - ./:/usr/src/app
23 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | SHELL := bash
2 |
3 | .PHONY: dev
4 |
5 | docker/build-redis:
6 | cd docker/redis; docker build -t copy-trader-redis .
7 |
8 | docker/run-redis:
9 | docker run -d -p6379:6379 --name copy-trader-redis copy-trader-redis
10 |
11 | docker/build:
12 | docker build -t copy-trader .
13 |
14 | docker/build-all: docker/build-redis docker/build
15 |
16 | docker/run:
17 | docker run -d -p 51585:51585 -v $(PWD):/usr/src/app --name copy-trader copy-trader
18 |
19 | docker/run-all:
20 | docker-compose up
21 |
22 | dev:
23 | clj -A:dev
24 |
25 | run:
26 | clj -M:run -m nrepl.cmdline --bind 0.0.0.0 --port 40404
27 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/websocket/security.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.websocket.security
2 | (:require
3 | [buddy.core.keys :as buddy-keys]
4 | [buddy.sign.jwt :as jwt]))
5 |
6 | (defonce ^:private ec-private-key (atom nil))
7 | (def ^:private ec-public-key (buddy-keys/public-key "public-key.pem"))
8 |
9 | (defn- load-private-key!
10 | []
11 | (when-not @ec-private-key
12 | (reset! ec-private-key (buddy-keys/private-key "private-key.pem"))))
13 |
14 | (defn sign-payload
15 | [payload]
16 | (load-private-key!)
17 | (jwt/sign payload @ec-private-key {:alg :es512}))
18 |
19 | (defn unsign-payload
20 | [signed-payload]
21 | (jwt/unsign signed-payload ec-public-key {:alg :es512}))
22 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/scheduling/websocket_keepalive_job.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.scheduling.websocket-keepalive-job
2 | (:require
3 | [clojure.tools.logging :as log]
4 | [clojurewerkz.quartzite.jobs :as jobs :refer [defjob]]
5 | [copy-trader.websocket.client :as ws-client]))
6 |
7 | (defn- do-websocket-keepalive-job*
8 | [_job-context]
9 | (ws-client/keepalive-clients!))
10 |
11 | (defjob websocket-keepalive-job
12 | [job-context]
13 | (try
14 | (do-websocket-keepalive-job* job-context)
15 | (catch Throwable t
16 | (log/error t))))
17 |
18 | (defn make-websocket-keepalive-job
19 | []
20 | (jobs/build
21 | (jobs/of-type websocket-keepalive-job)
22 | (jobs/using-job-data {})))
23 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/scheduling/jobs.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.scheduling.jobs
2 | (:require
3 | [copy-trader.scheduling.scheduler :as scheduler]
4 | [copy-trader.scheduling.trader-balances-job :as trader-balances-job]
5 | [copy-trader.scheduling.websocket-keepalive-job :as websocket-keepalive-job]))
6 |
7 | (defn schedule-jobs!
8 | []
9 | ;; TODO -- add more jobs here as needed
10 | (scheduler/schedule-cron-job!
11 | :fetch
12 | (websocket-keepalive-job/make-websocket-keepalive-job)
13 | ;; seconds minutes hours dom month dow
14 | ;; every 10 seconds
15 | "*/10 * * ? * *")
16 | (scheduler/schedule-cron-job!
17 | :fetch
18 | (trader-balances-job/make-trader-balances-job)
19 | ;; seconds minutes hours dom month dow
20 | ;; every 20 seconds
21 | "10,30,50 * * ? * *"))
22 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/system.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.system
2 | (:require
3 | [copy-trader.exchange.traders :as traders]
4 | [copy-trader.core :as core]
5 | [copy-trader.scheduling.scheduler :as scheduler]
6 | [copy-trader.scheduling.jobs :as jobs]
7 | [copy-trader.server :as server]
8 | [copy-trader.websocket.client :as ws]))
9 |
10 | (defn halt
11 | []
12 | (when (:is-running? @core/state)
13 | (ws/disconnect-clients!)
14 | (scheduler/halt!)
15 | (server/halt!)
16 | (reset! core/state {:is-running? false}))
17 | :halted)
18 |
19 | (defn go
20 | []
21 | (when-not (:is-running? @core/state)
22 | (swap! core/state assoc :is-running? true)
23 | (traders/load-traders!)
24 | (scheduler/start!)
25 | (server/start!)
26 | (jobs/schedule-jobs!)
27 | (ws/connect-clients!))
28 | :running)
29 |
--------------------------------------------------------------------------------
/example.client.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "servers": [
3 | {
4 | "uri": "wss://copy-trader.mershonenterprises.com:1585/ws"
5 | }
6 | ],
7 | "traders": [
8 | {
9 | "nickname": "My Alpaca Trader",
10 | "exchange": "alpaca",
11 | "assets": ["equity"],
12 | "credentials": {
13 | "api_key": "YOUR_API_TOKEN",
14 | "api_secret": "YOU_API_SECRET"
15 | },
16 | "max_positions": 20,
17 | "type": "paper",
18 | "leverage": 2.0,
19 | "risk": 0.0375,
20 | "shorting": false
21 | },
22 | {
23 | "nickname": "My Ameritrade Trader",
24 | "exchange": "ameritrade",
25 | "assets": ["equity"],
26 | "credentials": {
27 | "app_id": "YOUR_AMERITRADE_DEVELOPER_ACCOUNT_APP_ID",
28 | "account_id": "YOUR_AMERITRADE_ACCOUNT_ID"
29 | },
30 | "max_positions": 20,
31 | "type": "live",
32 | "leverage": 2.0,
33 | "risk": 0.0375,
34 | "shorting": false
35 | }
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/scheduling/trader_balances_job.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.scheduling.trader-balances-job
2 | (:require
3 | [clojure.tools.logging :as log]
4 | [clojurewerkz.quartzite.jobs :as jobs :refer [defjob]]
5 | [copy-trader.cache :refer [with-cache]]
6 | [copy-trader.exchange.traders :refer [all-traders]]
7 | [copy-trader.exchange.trader :refer [;;cache-orders! cache-positions!
8 | with-balance with-orders with-positions]]))
9 |
10 | (defn- update-trader-balances!
11 | [trader-state]
12 | (swap! trader-state (fn [trader-map]
13 | (->> trader-map
14 | with-balance
15 | with-positions
16 | with-orders
17 | with-cache))))
18 |
19 | (defn- do-trader-balances-job*
20 | [_job-context]
21 | (doseq [trader-state (all-traders)]
22 | (update-trader-balances! trader-state)))
23 |
24 | (defjob trader-balances-job
25 | [job-context]
26 | (try
27 | (do-trader-balances-job* job-context)
28 | (catch Throwable t
29 | (log/error t))))
30 |
31 | (defn make-trader-balances-job
32 | []
33 | (jobs/build
34 | (jobs/of-type trader-balances-job)
35 | (jobs/using-job-data {})))
36 |
--------------------------------------------------------------------------------
/src/clj/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.appender.R=org.apache.log4j.RollingFileAppender
2 | log4j.appender.R.File=log/system.log
3 | log4j.appender.R.MaxFileSize=4096KB
4 | log4j.appender.R.MaxBackupIndex=9
5 | log4j.appender.R.layout=org.apache.log4j.PatternLayout
6 | log4j.appender.R.layout.ConversionPattern=[%d][%p][%c] %m%n
7 | log4j.logger.R=INFO, R
8 |
9 | log4j.rootLogger=INFO, R
10 |
11 | log4j.appender.EXCHANGE_ALPACA=org.apache.log4j.RollingFileAppender
12 | log4j.appender.EXCHANGE_ALPACA.File=log/alpaca.log
13 | log4j.appender.EXCHANGE_ALPACA.MaxFileSize=4096KB
14 | log4j.appender.EXCHANGE_ALPACA.MaxBackupIndex=9
15 | log4j.appender.EXCHANGE_ALPACA.layout=org.apache.log4j.PatternLayout
16 | log4j.appender.EXCHANGE_ALPACA.layout.ConversionPattern=[%d][%p][%c] %m%n
17 | log4j.additivity.EXCHANGE_ALPACA=true
18 | log4j.logger.copy-trader.exchange.alpaca=INFO, EXCHANGE_ALPACA
19 |
20 | log4j.appender.WEBSOCKET=org.apache.log4j.RollingFileAppender
21 | log4j.appender.WEBSOCKET.File=log/websocket.log
22 | log4j.appender.WEBSOCKET.MaxFileSize=4096KB
23 | log4j.appender.WEBSOCKET.MaxBackupIndex=9
24 | log4j.appender.WEBSOCKET.layout=org.apache.log4j.PatternLayout
25 | log4j.appender.WEBSOCKET.layout.ConversionPattern=[%d][%p][%c] %m%n
26 | log4j.additivity.WEBSOCKET=true
27 | log4j.logger.copy-trader.websocket=DEBUG, WEBSOCKET
--------------------------------------------------------------------------------
/src/clj/copy_trader/exchange/traders.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.exchange.traders
2 | (:require
3 | [clojure.tools.logging :as log]
4 | [copy-trader.cache :as cache]
5 | [copy-trader.config :refer [config]]
6 | [copy-trader.core :as core]
7 | [copy-trader.exchange.trader :as trader]
8 | [copy-trader.exchange.alpaca.trader]
9 | [copy-trader.exchange.ameritrade.trader]))
10 |
11 | (defn load-traders!
12 | []
13 | (try
14 | (swap! core/state assoc :traders
15 | (mapv (comp atom
16 | cache/with-orders-and-positions
17 | cache/with-credentials)
18 | (:traders (config))))
19 | :ok
20 | (catch Throwable t
21 | (log/error t)
22 | (.getMessage t))))
23 |
24 | (defn all-traders
25 | []
26 | (:traders @core/state))
27 |
28 | (defn dispatch-trade!
29 | [{:keys [asset-type _symbol direction _order-type _price _stop-loss _percentage] :as trade-msg}]
30 | (let [traders (->> (:traders @core/state)
31 | (filter #(some #{asset-type} (:assets (deref %)))))]
32 | (doseq [trader traders]
33 | (let [{:keys [exchange shorting] :as trader-map} @trader]
34 | (when (or (= "long" direction)
35 | (and shorting (= "short" direction)))
36 | (trader/on-trade trader-map (assoc trade-msg :exchange exchange)))))))
37 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/exchange/alpaca/driver.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.exchange.alpaca.driver
2 | (:require
3 | [clj-http.conn-mgr :as conn-mgr]
4 | [clj-http.client :as client]))
5 |
6 | (defonce ^:private cm (conn-mgr/make-reusable-conn-manager {:threads 4}))
7 |
8 | (defn- endpoint-url
9 | [{trader-type :type :as _trader}]
10 | (if (= "paper" trader-type)
11 | "https://paper-api.alpaca.markets/v2/"
12 | "https://api.alpaca.markets/v2/"))
13 |
14 | (defn- alpaca-request*
15 | [{:keys [credentials] :as trader-map} client-fn path & {:keys [params]}]
16 | {:pre [(map? credentials) (fn? client-fn) (or (nil? path) (string? path))]}
17 | (client-fn
18 | (str (endpoint-url trader-map) path)
19 | {:connection-manager cm
20 | :content-type :json
21 | :as :json
22 | :headers {"APCA-API-KEY-ID" (:api_key credentials)
23 | "APCA-API-SECRET-KEY" (:api_secret credentials)}
24 | :form-params params
25 | :socket-timeout 5000
26 | :connection-timeout 5000
27 | :throw-exceptions false}))
28 |
29 | (defn alpaca-get
30 | [trader-map path]
31 | (some-> (alpaca-request* trader-map client/get path)
32 | :body))
33 |
34 | (defn alpaca-post!
35 | [trader-map path params]
36 | (alpaca-request* trader-map client/post path :params params))
37 |
38 | (defn alpaca-delete!
39 | [trader-map path]
40 | (alpaca-request* trader-map client/delete path))
41 |
--------------------------------------------------------------------------------
/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src/clj"]
2 | :deps {org.clojure/clojure {:mvn/version "1.10.3"}
3 | org.clojure/clojurescript {:mvn/version "1.10.844"}
4 | environ/environ {:mvn/version "1.2.0"}
5 | nrepl/nrepl {:mvn/version "0.9.0-beta5"}
6 |
7 | ;; logging
8 | org.clojure/tools.logging {:mvn/version "1.1.0"}
9 | org.slf4j/slf4j-log4j12 {:mvn/version "1.7.7"}
10 |
11 | ;; cache
12 | com.taoensso/carmine {:mvn/version "3.1.0"}
13 |
14 | ;; memoization
15 | org.clojure/core.memoize {:mvn/version "1.0.236"}
16 |
17 | ;; http
18 | clj-http/clj-http {:mvn/version "3.12.3"}
19 | ring/ring-core {:mvn/version "1.9.5"}
20 | ring/ring-devel {:mvn/version "1.9.5"}
21 | ring/ring-defaults {:mvn/version "0.3.3"}
22 | buddy/buddy-auth {:mvn/version "2.1.0"}
23 | metosin/reitit {:mvn/version "0.5.18"}
24 | metosin/muuntaja {:mvn/version "0.6.8"}
25 | info.sunng/ring-jetty9-adapter {:mvn/version "0.14.3"}
26 |
27 | ;; scheduling
28 | clojurewerkz/quartzite {:mvn/version "2.1.0"}
29 |
30 | ;; json parsing
31 | cheshire/cheshire {:mvn/version "5.10.2"}
32 |
33 | ;; client websockets
34 | stylefruits/gniazdo {:mvn/version "1.1.4"}}
35 | :aliases {:dev {:extra-paths ["src/dev"]}
36 | :run {:extra-paths ["src/prod"]}}}
37 |
--------------------------------------------------------------------------------
/doc/Ameritrade.md:
--------------------------------------------------------------------------------
1 | Ameritrade Traders
2 | ==
3 |
4 | Caveats
5 | --
6 | Firstly, Ameritrade **ONLY** has live mode trading, despite ThinkOrSwim
7 | supporting paper trading.
8 |
9 | Accounts must have the Margin Trading feature enabled to short.
10 |
11 | IRAs and Securities-Backed Collateral accounts cannot have margin and so shorting must be disabled to trade against those.
12 |
13 | You may only have one application ID per account and that means trading all your accounts in Ameritrade (if you have multiple) is not currently supported.
14 |
15 | Configuration
16 | --
17 |
18 | 1. Run the copy-trader
19 | 1. Take note of your TD Ameritrade Account ID. It's a number.
20 | 1. Read https://developer.tdameritrade.com/content/authentication-faq and
21 | https://developer.tdameritrade.com/content/simple-auth-local-apps
22 | 1. Register a Developer account at https://developer.tdameritrade.com
23 |
24 | Your developer account **IS NOT THE SAME** as your tdameritrade.com or thinkorswim.com account.
25 |
26 | 1. Create an Application, specifying the `redirect URL` as
27 | `http://localhost:51585/ACCOUNT_ID/authorize` where `ACCOUNT_ID` is your TD
28 | Ameritrade Account ID from step 1.
29 |
30 | 1. Successfully creating an application will give you the Application ID.
31 |
32 | 1. Navigate your web browser to
33 | https://auth.tdameritrade.com/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A51585%2Fauthorize%2FACCOUNT_ID&client_id=APPLICATION_ID%40AMER.OAUTHAP
34 | where `ACCOUNT_ID` is your TD Ameritrade Account ID from step 1 and
35 | `APPLICATION_ID` is your Application ID from the previous step.
36 |
37 | 1. After logging in and authorizing the app, it should redirect you back to your
38 | copy-trader instance, but the URL may have `https` instead of `http` at the
39 | beginning. If so, delete the `s` and load the URL and it should authenticate
40 | correctly and send you to a page saying so.
41 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/scheduling/scheduler.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.scheduling.scheduler
2 | (:require
3 | [clojurewerkz.quartzite.scheduler :as scheduler]
4 | [clojurewerkz.quartzite.schedule.cron :as cron]
5 | [clojurewerkz.quartzite.triggers :as triggers]
6 | [copy-trader.core :as core]))
7 |
8 | (defonce ^:private schedulers-atom (atom {}))
9 |
10 | (defn is-scheduler-running?
11 | []
12 | (:is-scheduler-running? @core/state))
13 |
14 | (defn start!
15 | []
16 | (when (empty? @schedulers-atom)
17 | (reset! schedulers-atom
18 | {:default (scheduler/initialize)
19 | :fetch (scheduler/initialize)
20 | :trader (scheduler/initialize)}))
21 |
22 | (when-not (is-scheduler-running?)
23 | (do
24 | (scheduler/start (:default @schedulers-atom))
25 | (scheduler/start (:fetch @schedulers-atom))
26 | (scheduler/start (:trader @schedulers-atom))
27 | (swap! core/state assoc :is-scheduler-running? true)
28 | :started)))
29 |
30 | (defn halt!
31 | []
32 | (let [schedulers @schedulers-atom]
33 | (when (and (not-empty schedulers)
34 | (is-scheduler-running?))
35 | (doseq [sched-key (keys schedulers)]
36 | (scheduler/shutdown (get schedulers sched-key))
37 | (swap! core/state assoc :is-scheduler-running? false))
38 | (reset! schedulers-atom {})
39 | :halted)))
40 |
41 | (defn schedule-cron-job!
42 | ([target-scheduler job cron-schedule-string]
43 | {:pre [(keyword? target-scheduler) (string? cron-schedule-string)]}
44 | (schedule-cron-job! target-scheduler job cron-schedule-string false))
45 | ([target-scheduler job cron-schedule-string skip-misfires?]
46 | {:pre [(string? cron-schedule-string) (boolean? skip-misfires?)]}
47 | (let [job-schedule (cron/schedule
48 | (cron/cron-schedule cron-schedule-string)
49 | (cron/in-time-zone (java.util.TimeZone/getTimeZone "UTC")))
50 | job-schedule (if skip-misfires?
51 | (cron/with-misfire-handling-instruction-do-nothing job-schedule)
52 | job-schedule)
53 | job-trigger (triggers/build
54 | (triggers/start-now)
55 | (triggers/with-schedule job-schedule))]
56 | (scheduler/schedule
57 | (get @schedulers-atom target-scheduler)
58 | job
59 | job-trigger))))
60 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/exchange/trader.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.exchange.trader
2 | (:refer-clojure :exclude [symbol]))
3 |
4 | (defmulti with-balance
5 | (fn [{:keys [exchange] :as _trader}]
6 | exchange))
7 |
8 | (defmulti with-orders
9 | (fn [{:keys [exchange] :as _trader}]
10 | exchange))
11 |
12 | (defmulti with-positions
13 | (fn [{:keys [exchange] :as _trader}]
14 | exchange))
15 |
16 | (defmulti on-trade
17 | (fn [trader-map {:keys [exchange symbol direction order-type _price _stop-loss _percentage]}]
18 | {:pre [(map? trader-map) (string? exchange) (string? symbol)
19 | (string? direction) (string? order-type)]}
20 | [exchange direction order-type]))
21 |
22 | (defmethod on-trade :default
23 | [_ _]
24 | :not-implemented)
25 |
26 | (defmulti with-authorization
27 | (fn [{:keys [exchange] :as _trader-map} _code]
28 | exchange))
29 |
30 | (defmethod with-authorization :default
31 | [_ _]
32 | :not-implemented)
33 |
34 | (defn active-positions
35 | [{:keys [open-positions] :as _trader-map}]
36 | (reduce-kv
37 | (fn [m symbol {:keys [volume]}]
38 | (if (pos? volume)
39 | (assoc m symbol volume)
40 | m))
41 | {}
42 | open-positions))
43 |
44 | (defn compute-position-size
45 | [{:keys [balance-usd leverage max_positions] :as trader-map}]
46 | (let [active (active-positions trader-map)
47 | active-position-count (->> active keys count)]
48 | (if (and (< active-position-count max_positions))
49 | (/ (* balance-usd leverage)
50 | max_positions)
51 | 0.0)))
52 |
53 | (defn compute-volume
54 | [{:keys [risk leverage max_positions] :as trader-map} price high low]
55 | (let [diff (- high low)
56 | balance-usd (/ (* (:balance-usd trader-map) leverage)
57 | max_positions)
58 | ;; use a minimum of the capital allocated, or don't take the trade
59 | min-usage (/ (* balance-usd 0.20)
60 | price)
61 | max-usage balance-usd
62 | ;; we never want to risk more than our tolerance
63 | max-risk (* risk max-usage)
64 | max-trade-size (if (pos? diff)
65 | (/ max-risk diff)
66 | 0.0)
67 | ;; this is the maximum amount we COULD buy
68 | max-purchase-power (/ max-usage price)
69 | ;; but this is the max we CHOOSE to buy
70 | desired-volume (min max-purchase-power max-trade-size)]
71 | ;; don't take the trade unless we're meeting minimum volume requirements
72 | (if (> desired-volume min-usage)
73 | desired-volume
74 | 0.0)))
75 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/server.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.server
2 | (:require
3 | [copy-trader.core :as core]
4 | [copy-trader.exchange.trader :as trader]
5 | [copy-trader.websocket.server :as ws-server]
6 | [muuntaja.core :as m]
7 | [reitit.coercion.spec :as rcs]
8 | [reitit.ring :as ring]
9 | [reitit.ring.coercion :as rrc]
10 | [reitit.ring.middleware.muuntaja :as muuntaja]
11 | [reitit.ring.middleware.parameters :as parameters]
12 | [ring.adapter.jetty9 :as jetty])
13 | (:import
14 | [org.eclipse.jetty.server Server]))
15 |
16 | (def ^:private default-route-data
17 | {:muuntaja (m/create m/default-options)
18 | :coercion rcs/coercion
19 | :middleware [parameters/parameters-middleware
20 | muuntaja/format-middleware
21 | rrc/coerce-exceptions-middleware
22 | rrc/coerce-request-middleware
23 | rrc/coerce-response-middleware]})
24 |
25 | (defn- index [_]
26 | {:status 401
27 | :headers {"Content-Type" "text/html"}
28 | :body "Access Denied"})
29 |
30 | (defn authorize-trader-with-account-id!
31 | [{:keys [account-id code]}]
32 | (when-let [trader-state (->> @core/state
33 | :traders
34 | (filter (fn [trader-state]
35 | (= account-id (-> @trader-state
36 | :credentials
37 | :account_id)))))]
38 | (swap! trader-state #(trader/with-authorization % code)))
39 | {:status 202
40 | :headers {"Content-Type" "text/html"}
41 | :body "Accepted"})
42 |
43 | (def http-handler
44 | (ring/ring-handler
45 | (ring/router
46 | [["/" {:get index}]
47 | ["/:account-id/authorize" {:any {:parameters {:query {:code string?}}}
48 | :handler (fn [req]
49 | (authorize-trader-with-account-id!
50 | {:account-id (-> req :path-params :account-id)
51 | :code (-> req :parameters :query :code)}))}]]
52 | {:data default-route-data
53 | :conflicts (constantly nil)})
54 | (ring/routes
55 | (ring/create-default-handler))))
56 |
57 | (defn start!
58 | []
59 | (swap! core/state assoc :http-server
60 | (jetty/run-jetty http-handler
61 | {:port 51585
62 | :join? false
63 | :websockets {"/ws" ws-server/handler}})))
64 |
65 | (defn halt!
66 | []
67 | (when-let [server (:http-server @core/state)]
68 | (.stop ^Server server)
69 | (swap! core/state dissoc :http-server)))
70 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/cache.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.cache
2 | (:refer-clojure :exclude [key symbol])
3 | (:require
4 | [clojure.walk :refer [keywordize-keys]]
5 | [environ.core :refer [env]]
6 | [taoensso.carmine :as car]))
7 |
8 | (defn redis-uri
9 | []
10 | (or (env :redis-endpoint)
11 | "redis://127.0.0.1:6379"))
12 |
13 | (def ^:private redis-conn {:pool {} :spec {:uri (redis-uri)}})
14 | (defmacro with-carmine [& body] `(car/wcar redis-conn ~@body))
15 |
16 | (defn- save!
17 | []
18 | (with-carmine
19 | (car/save)))
20 |
21 | (defn- del!
22 | [key]
23 | (with-carmine
24 | (car/del key)))
25 |
26 | (defn- hset!
27 | [key field value]
28 | (with-carmine
29 | (car/hset key field value)))
30 |
31 | (defn- hdel!
32 | [key field]
33 | (with-carmine
34 | (car/hdel key field)))
35 |
36 | (defn- hget
37 | [key field]
38 | (with-carmine
39 | (car/hget key field)))
40 |
41 | (defn- hgetall
42 | [key]
43 | (apply hash-map
44 | (with-carmine
45 | (car/hgetall key))))
46 |
47 | (defn cache-credentials!
48 | [{:keys [nickname credentials] :as _trader-map}]
49 | (let [cache-key (str nickname "-credentials")]
50 | (with-carmine
51 | (car/set cache-key credentials))))
52 |
53 | (defn get-credentials
54 | [{:keys [nickname] :as _trader-map}]
55 | (let [cache-key (str nickname "-credentials")]
56 | (with-carmine
57 | (car/get cache-key))))
58 |
59 | (defn with-credentials
60 | [trader-map]
61 | (update-in trader-map [:credentials] merge (get-credentials trader-map)))
62 |
63 | (defn cache-orders!
64 | [{:keys [nickname orders] :as _trader-map}]
65 | (let [cache-key (str nickname "-orders")]
66 | (del! cache-key)
67 | (doseq [{:keys [status order-id] :as order} orders]
68 | (if (= :open status)
69 | (hset! cache-key order-id order)
70 | (hdel! cache-key order-id)))))
71 |
72 | (defn get-orders
73 | [{:keys [nickname] :as _trader-map}]
74 | (let [cache-key (str nickname "-orders")]
75 | (or
76 | (some->> (hgetall cache-key)
77 | vals
78 | vec)
79 | [])))
80 |
81 | (defn cache-positions!
82 | [{:keys [nickname open-positions] :as _trader-map}]
83 | (let [cache-key (str nickname "-positions")]
84 | (del! cache-key)
85 | (doseq [[symbol position] open-positions]
86 | (hset! cache-key symbol position))))
87 |
88 | (defn get-positions
89 | [{:keys [nickname] :as _trader-map}]
90 | (let [cache-key (str nickname "-positions")]
91 | (keywordize-keys (hgetall cache-key))))
92 |
93 | (defn with-orders-and-positions
94 | [trader-map]
95 | (merge trader-map
96 | {:orders (get-orders trader-map)
97 | :open-positions (get-positions trader-map)}))
98 |
99 | (defn with-cache
100 | [trader-map]
101 | ((juxt cache-credentials! cache-orders! cache-positions!)
102 | trader-map)
103 | (save!)
104 |
105 | ;; return
106 | trader-map)
107 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/websocket/client.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.websocket.client
2 | (:require
3 | [cheshire.core :refer [parse-string generate-string]]
4 | [clojure.core.memoize :as memo]
5 | [clojure.tools.logging :as log]
6 | [copy-trader.config :refer [config]]
7 | [copy-trader.core :as core]
8 | [copy-trader.exchange.traders :as traders]
9 | [copy-trader.websocket.event :as ws-event]
10 | [copy-trader.websocket.security :as security]
11 | [gniazdo.core :as ws]))
12 |
13 | (defn send-to-server!
14 | [socket {:keys [_message-code _payload] :as msg}]
15 | (ws/send-msg socket (generate-string msg)))
16 |
17 | (defmethod ws-event/on-event :auth-challenge
18 | [uri {:keys [payload] :as _msg}]
19 | (let [socket (get-in @core/state [:ws-servers uri])
20 | auth-challenge-payload (security/unsign-payload payload)]
21 | (send-to-server! socket {:message-code :auth-challenge-ack
22 | :payload auth-challenge-payload})))
23 |
24 | (defmethod ws-event/on-event :pong
25 | [_uri _msg]
26 | :pong)
27 |
28 | ;; receiving a trade signal from our server
29 | (defmethod ws-event/on-event :trade
30 | [uri {:keys [payload] :as _msg}]
31 | (let [trade-payload (security/unsign-payload payload)]
32 | (log/info "Received trade from server " trade-payload)
33 | ;; re-fire event to all downstream clients
34 | (ws-event/on-event uri {:message-code :refire-trade-down
35 | :payload payload})
36 | (traders/dispatch-trade! trade-payload)))
37 |
38 | ;; refiring a trade signal from our client
39 | (defmethod ws-event/on-event :refire-trade-up
40 | [_uri {:keys [_message-code _payload] :as msg}]
41 | (doseq [[_uri socket] (:ws-servers @core/state)]
42 | (send-to-server! socket (assoc msg :message-code :trade))))
43 |
44 | (defn- ping!
45 | [socket]
46 | (send-to-server! socket {:message-code :ping
47 | :payload {:time (System/currentTimeMillis)}}))
48 |
49 | (defn- on-receive*
50 | [uri json-message]
51 | (let [edn-msg (-> json-message
52 | (parse-string true)
53 | (update :message-code keyword))]
54 | (log/debug (str "Received message from " uri) edn-msg)
55 | (ws-event/on-event uri edn-msg)))
56 |
57 | (def ^:private memoized-on-receive
58 | (memo/ttl on-receive* {} :ttl/threshold ws-event/ONE-DAY))
59 |
60 | (defn- on-error
61 | [& args]
62 | (log/error args))
63 |
64 | (defn- on-close
65 | [uri]
66 | (log/info (format "Disconnecting from %s" uri))
67 | (when-let [socket (get-in @core/state [:ws-servers uri])]
68 | (try
69 | (ws/close socket)
70 | (catch Throwable _t
71 | :ok))
72 | (swap! core/state update-in [:ws-servers] dissoc uri)
73 | :disconnected))
74 |
75 | (defn connect!
76 | [uri]
77 | (log/info (format "Connecting to %s" uri))
78 | (when (:is-running? @core/state)
79 | (when-let [socket (ws/connect uri
80 | :on-receive (fn [msg]
81 | (memoized-on-receive uri msg))
82 | :on-error on-error
83 | :on-close (fn [& args]
84 | (log/error args)
85 | (on-close uri)))]
86 | (swap! core/state assoc-in [:ws-servers uri] socket)
87 | :connected)))
88 |
89 | (defn disconnect!
90 | [uri]
91 | ;; TODO -- maybe we'll want to send unsubscribe events here
92 | (on-close uri))
93 |
94 | (defn connect-clients!
95 | []
96 | (try
97 | (let [server-configs (:servers (config))]
98 | (doseq [{:keys [uri]} server-configs]
99 | (connect! uri)))
100 | :connected))
101 |
102 | (defn disconnect-clients!
103 | []
104 | (let [connected-clients (:ws-servers @core/state)]
105 | (doseq [[uri _socket] connected-clients]
106 | (disconnect! uri))))
107 |
108 | (defn keepalive-clients!
109 | []
110 | (let [server-configs (:servers (config))]
111 | (doseq [{:keys [uri]} server-configs]
112 | (locking uri
113 | (let [socket (get-in @core/state [:ws-servers uri])]
114 | (if-not socket
115 | (connect! uri)
116 |
117 | (try
118 | (ping! socket)
119 | (catch Throwable _t
120 | (disconnect! uri)
121 | (connect! uri)))))))))
122 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/websocket/server.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.websocket.server
2 | (:require
3 | [cheshire.core :refer [parse-string generate-string]]
4 | [clojure.core.memoize :as memo]
5 | [clojure.tools.logging :as log]
6 | [copy-trader.core :as core]
7 | [copy-trader.exchange.traders :as traders]
8 | [copy-trader.websocket.event :as ws-event]
9 | [copy-trader.websocket.security :as security]
10 | [ring.adapter.jetty9 :as jetty])
11 | (:import [java.util UUID]))
12 |
13 | (defn- send-to-client!
14 | [ws {:keys [_message-code _payload] :as msg}]
15 | (if (jetty/connected? ws)
16 | (locking ws
17 | (jetty/send! ws (generate-string msg)))
18 | (swap! core/state update :ws-clients dissoc ws)))
19 |
20 | (defn send-to-clients!
21 | [message & {:keys [except-client]}]
22 | (doseq [[ws attrs] (:ws-clients @core/state)]
23 | (when (and (:authenticated? attrs)
24 | (not= except-client ws))
25 | (try
26 | (send-to-client! ws message)
27 | (catch Throwable t
28 | (log/error t))))))
29 |
30 | (defmulti on-event
31 | (fn [_ws {:keys [message-code payload]}]
32 | {:pre [(keyword? message-code) (seqable? payload)]}
33 | message-code))
34 |
35 | (defmethod on-event :default
36 | [_ws {:keys [_message-code payload]}]
37 | (log/error "Unrecognized websocket command:" payload))
38 |
39 | (defmethod on-event :auth-challenge-ack
40 | [ws {:keys [_message-code payload]}]
41 | (let [auth-challenge (get-in @core/state [:ws-clients ws :auth-challenge])]
42 | (if (= auth-challenge (:auth-challenge payload))
43 | (do
44 | (log/info "WebSocket client authenticated.")
45 | (swap! core/state assoc-in [:ws-clients ws :authenticated?] true))
46 | (try
47 | (log/error "WebSocket failed auth challenge. Disconnecting.")
48 | (swap! core/state update :ws-clients dissoc ws)
49 | (jetty/close! ws)
50 | (catch Throwable t
51 | (log/error t))))))
52 |
53 | ;; re-firing a trade signal from our server
54 | (defmethod ws-event/on-event :refire-trade-down
55 | [_uri {:keys [_message-code payload]}]
56 | {:pre [(string? payload)]}
57 | ;; payload is already signed
58 | ;; re-fire event to all downstream clients
59 | (send-to-clients! {:message-code :trade
60 | :payload payload}))
61 |
62 | ;; receiving a trade signal from our client
63 | (defmethod on-event :trade
64 | [ws {:keys [_message-code payload] :as msg}]
65 | (try
66 | (let [trade-payload (-> (security/unsign-payload payload))]
67 | (log/info "Received trade from client" trade-payload)
68 | ;; re-fire event to other downstream clients
69 | (send-to-clients! (assoc msg :message-code :trade)
70 | :except-client ws)
71 | ;; re-fire event to our server
72 | (ws-event/on-event "" {:message-code :refire-trade-up
73 | :payload payload})
74 | (traders/dispatch-trade! trade-payload))
75 | (catch Throwable t
76 | (log/error t))))
77 |
78 | (defmethod on-event :ping
79 | [ws _msg]
80 | (send-to-client! ws {:message-code :pong
81 | :payload {:time (System/currentTimeMillis)}}))
82 |
83 | (defn- on-connect
84 | [ws]
85 | (log/info "WebSocket client connected")
86 | (let [auth-challenge (str (UUID/randomUUID))]
87 | (swap! core/state update-in [:ws-clients ws]
88 | merge {:authenticated? false
89 | :auth-challenge auth-challenge
90 | :subscriptions #{}})
91 | (send-to-client! ws {:message-code :auth-challenge
92 | :payload (security/sign-payload
93 | {:auth-challenge auth-challenge})})))
94 |
95 | (defn- on-error
96 | [_ws err]
97 | (log/error err "WebSocket error"))
98 |
99 | (defn- on-close
100 | [ws status-code reason]
101 | (log/info "WebSocket client disconnected" status-code reason)
102 | (swap! core/state update :ws-clients dissoc ws))
103 |
104 | (defn- on-receive*
105 | [ws json-message]
106 | (try
107 | (let [edn-msg (-> json-message
108 | (parse-string true)
109 | (update :message-code keyword))]
110 | (log/debug "Received message from client" edn-msg)
111 | (on-event ws edn-msg))
112 | (catch Throwable t
113 | (log/error t))))
114 |
115 | (def ^:private memoized-on-receive
116 | (memo/ttl on-receive* {} :ttl/threshold ws-event/ONE-DAY))
117 |
118 | (def handler
119 | ;; GOTCHA -- these need to be wrapped for hot reloading to work
120 | {:on-connect (fn [ws] (on-connect ws))
121 | :on-error (fn [ws err] (on-error ws err))
122 | :on-close (fn [ws status-code reason] (on-close ws status-code reason))
123 | :on-text (fn [ws text] (memoized-on-receive ws text))})
124 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | copy-trader
2 | ==
3 | This is an engine to facilitate copy-trading syndicated trades by other trusted
4 | traders over the internet.
5 |
6 | Project Status
7 | --
8 |
9 | Status: MVP Feature Complete, **NOW IN TESTING**
10 |
11 | Core development is done and the system is working and [a reference
12 | implementation paired with Impresario is available for the brave and
13 | bold](https://gist.github.com/kevinmershon/240bf57b25f2c595806e213ac6dcf944).
14 |
15 | ### To Do
16 |
17 | * "Watch Account" mode for syndicating manual trades
18 | * Test Ameritrade auth and trading (feature complete but currently untested!)
19 |
20 | How it Works
21 | --
22 | This application acts as both a WebSocket client and server on port `51585`.
23 |
24 | Client authentication is performed using a 512-bit EC public/private key pair
25 | that you must either generate yourself, or if you are subscribing to syndicated
26 | trades of others, you must receive their `public-key.pem` file and put it in the
27 | project working directory.
28 |
29 | Only nodes having the `private-key.pem` file that pairs with the public key may
30 | emit valid trade messages themselves, however every node in the network will
31 | route trade messages to their immediate connections. Because of this, if you are
32 | running your own trade syndication network, be very careful with whom you share
33 | the private key (as in any case with cryptographic signing use cases), as anyone
34 | with the file running this project will be able to dispatch trade signals to the
35 | entire network.
36 |
37 | Trade messages are themselves rudimentary, only describing the attributes of the
38 | trade itself: long or short, limit or stop-loss, symbol, type, and price
39 | information.
40 |
41 | Disclaimers and Risks
42 | --
43 | 1. **You are 100% responsible for any trades made on your accounts, with or
44 | without this software.**
45 | 1. **Pattern Day Trader** violations: [FINRA
46 | requires](https://www.investopedia.com/terms/p/patterndaytrader.asp) you to
47 | have at least $25,000 in an account in order to execute day trades. If the
48 | nodes issuing trade messages to your network are day trading and your account
49 | has less than the mandated minimum, your account could be suspended for 3
50 | days. Repeated offenses could see your account suspended or closed, depending
51 | on your brokerage.
52 | 1. **There is no logic within this system for managing a position once opened.**
53 | 1. Once you are in a position, you either need to manage it yourself, or trust
54 | that the node that dispatched the trade message will accordingly follow-up
55 | with take-profit and stop-loss adjustments.
56 | 1. For very large networks, slippage and missed trades (orders that are opened
57 | but never filled) are virtually guaranteed, especially for illiquid markets
58 | (symbols that have low trade volume).
59 | 1. If you are joining a third-party network and not hosting your own, keep in
60 | mind that trusted nodes in the network are almost certainly front-running
61 | trades they share with the network, for their own personal gain.
62 |
63 | Generating public/private key pairs
64 | --
65 | These instructions could change depending on your operating system. The
66 | following commands work if you have `openssl` and `ssh` installed:
67 |
68 | ```
69 | openssl ecparam -genkey -name secp521r1 -noout -out private-key.pem
70 | chmod 400 private-key.pem
71 | ssh-keygen -f private-key.pem -e -m pem > public-key.pem
72 | ```
73 |
74 | Configuration
75 | --
76 | For Ameritade, please see the [Ameritrade Instructions](doc/Ameritrade.md).
77 |
78 | 1. Copy `example.client.config.json` to `config.json`, fill in your API key and
79 | secret, and adjust settings as desired. Remove unused client configs if you
80 | don't have a brokerage account for that exchange.
81 | 1. `type` valid values are `paper` and `live`
82 | 1. `max_positions` valid values above 1, recommended values between 15 and 20
83 | 1. `leverage` valid values range between 0.5 and 2.0
84 | 1. `risk` is a percentage range between 0.01 and 1.00, recommended values between 3% and 10%
85 | 1. `shorting` should probably be `true` unless you are trading on a cash-only account, like an IRA
86 |
87 | Build and run locally
88 | --
89 | In all cases you should have `make` / build-essential installed.
90 |
91 | ### Dependencies
92 | 1. Redis running locally on port `6379`
93 | 1. Clojure 1.10+ and `rlwrap`
94 |
95 | ### Running
96 | Run `make dev` to start
97 |
98 | Build and run with Docker / Docker-Compose
99 | --
100 | 1. `make docker/build-all`
101 | 1. `make docker/run-all`
102 |
103 | License
104 | ==
105 | AGPLv3
106 |
107 | FAQ
108 | ==
109 | 1. Is this legal?
110 |
111 | Probably, but if not someone should definitely take [these
112 | guys](https://copytrader.pro/) down. In theory this is just as legal as
113 | [following the trades of US Senators](https://senatestockwatcher.com/) and
114 | [buying bitcoin every time Elon Musk tweets about
115 | it](https://github.com/CyberPunkMetalHead/bitcoin-bot-buy-if-elon-tweets).
116 |
117 | 1. Is this a good idea?
118 |
119 | That depends entirely on whose trades you follow, and the general phase of the market at the time you start trading.
120 |
121 | 1. Will I lose money?
122 |
123 | Almost certainly at times, but if you are following a profitable trader then
124 | by leveraging this or any software based approach for trade syndication, you
125 | will likely make just about as much money as they do.
126 |
127 | Be aware that up to a 20% drawdown (from highest gain) in a year is pretty
128 | typical of most "safe" investment classes (e.g. Vanguard ETFs), but losing
129 | 90% on your account due to YOLO plays is ill advised.
130 |
131 | Similar Projects
132 | ==
133 | * https://github.com/MohammedRashad/Crypto-Copy-Trader
134 | * https://github.com/jiowcl/MQL-CopyTrade
135 | * https://github.com/kr0st/trade_replicator
136 | * https://github.com/zignaly-open/zignaly-webapp
137 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/exchange/ameritrade/driver.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.exchange.ameritrade.driver
2 | (:require
3 | [clj-http.client :as client]
4 | [clj-http.conn-mgr :as conn-mgr]
5 | [clj-time.coerce :as ctc]
6 | [clj-time.core :as ctcore]
7 | [clj-time.format :as ctf]
8 | [copy-trader.util :refer [atom?]]
9 | [copy-trader.exchange.trader :as trader]))
10 |
11 | (def ^:private cm (clj-http.conn-mgr/make-reusable-conn-manager {:threads 4}))
12 |
13 | (defn ameritrade-request*
14 | [trader-map client-fn path & {:keys [query-params params no-authorization?]
15 | :or {query-params {}
16 | params {}
17 | no-authorization? false}}]
18 | {:pre [(map? trader-map) (fn? client-fn) (or (nil? path) (string? path))]}
19 | (client-fn
20 | (str "https://api.tdameritrade.com/v1/" path)
21 | {:connection-manager cm
22 | :content-type (if no-authorization?
23 | :x-www-form-urlencoded
24 | :json)
25 | :as :json
26 | :headers (merge
27 | {}
28 | (when-not no-authorization?
29 | {"Authorization"
30 | (str "Bearer " (get-in trader-map [:credentials :access_token]))}))
31 | :query-params query-params
32 | :form-params params
33 | :socket-timeout 5000
34 | :connection-timeout 5000
35 | :throw-exceptions false}))
36 |
37 | (defn ameritrade-get
38 | [trader-map path & {:keys [params]
39 | :or {params {}}}]
40 | (some-> (ameritrade-request* trader-map client/get path :query-params params)
41 | :body))
42 |
43 | (defn ameritrade-post!
44 | [trader-map path params & {:keys [no-authorization?]
45 | :or {no-authorization? false}}]
46 | (ameritrade-request* trader-map client/post path
47 | :params params
48 | :no-authorization? no-authorization?))
49 |
50 | (defn ameritrade-delete!
51 | [trader-map path]
52 | (ameritrade-request* trader-map client/delete path))
53 |
54 | (defn- handle-authorize-response!
55 | [trader-map response]
56 | {:pre [(map? trader-map) (map? response)]}
57 | (let [updated-credentials
58 | (merge
59 | (when (:refresh_token_expires_in response)
60 | (let [refresh-token-expires (->> (ctcore/seconds (:refresh_token_expires_in response))
61 | (ctcore/plus (ctcore/now))
62 | ctc/to-sql-time)]
63 | {:refresh_token (:refresh_token response)
64 | :refresh_token_expires refresh-token-expires}))
65 | (when (:expires_in response)
66 | (let [access-token-expires (->> (ctcore/seconds (:expires_in response))
67 | (ctcore/plus (ctcore/now))
68 | ctc/to-sql-time)]
69 | {:access_token (:access_token response)
70 | :access_token_expires access-token-expires})))]
71 | ;; FIXME -- persist refresh and access token to Redis?
72 | (update-in trader-map [:credentials] merge updated-credentials)))
73 |
74 | (defn refresh-token!
75 | [trader-state & {:keys [full-refresh?]
76 | :or {full-refresh? false}}]
77 | {:pre [(atom? trader-state)]}
78 | (let [trader-map @trader-state
79 | refresh-token (-> trader-map :credentials :refresh_token)
80 | app-id (-> trader-map :credentials :app_id)
81 | response (-> (ameritrade-post! trader-map "oauth2/token"
82 | (merge
83 | (when full-refresh?
84 | {:access_type "offline"})
85 | {"grant_type" "refresh_token"
86 | "refresh_token" refresh-token
87 | "client_id" app-id})
88 | :no-authorization? true)
89 | :body)]
90 | (swap! trader-state #(handle-authorize-response! % response))
91 | :refreshed))
92 |
93 | (defn maybe-refresh-tokens!
94 | [trader-state]
95 | {:pre [(atom? trader-state)]}
96 | (let [{:keys [refresh_token_expires access_token_expires]}
97 | (:credentials @trader-state)]
98 | (when (and (not (nil? refresh_token_expires))
99 | (not (nil? access_token_expires)))
100 | (cond
101 | (ctcore/before? (if (string? refresh_token_expires)
102 | (ctf/parse
103 | (ctf/formatters :date-time-no-ms)
104 | refresh_token_expires)
105 | (ctc/from-sql-time refresh_token_expires))
106 | (ctcore/plus (ctcore/now)
107 | (ctcore/days 1)))
108 | (refresh-token! trader-state :full-refresh? true)
109 |
110 | (ctcore/before? (if (string? access_token_expires)
111 | (ctf/parse
112 | (ctf/formatters :date-time-no-ms)
113 | access_token_expires)
114 | (ctc/from-sql-time access_token_expires))
115 | (ctcore/plus (ctcore/now)
116 | (ctcore/minutes 5)))
117 | (refresh-token! trader-state)))))
118 |
119 | (defn ->account-id
120 | [trader-map]
121 | (get-in trader-map [:credentials :account_id]))
122 |
123 | (defn ->redirect-url
124 | [trader-map]
125 | (let [account-id (->account-id trader-map)]
126 | (str "http://localhost:51581/%s/authorize" account-id)))
127 |
128 | (defmethod trader/with-authorization "ameritrade"
129 | [trader-map {:keys [code]}]
130 | {:pre [(map? trader-map) (string? code)]}
131 | (let [app-id (:app_id (:credentials trader-map))
132 | response (-> (ameritrade-post! trader-map "oauth2/token"
133 | {"grant_type" "authorization_code"
134 | "refresh_token" ""
135 | "access_type" "offline"
136 | "code" code
137 | "client_id" (str app-id "@AMER.OAUTHAP")
138 | "redirect_uri" (->redirect-url trader-map)}
139 | :no-authorization? true)
140 | :body)]
141 | (handle-authorize-response! trader-map response)))
142 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/exchange/alpaca/trader.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.exchange.alpaca.trader
2 | (:refer-clojure :exclude [symbol])
3 | (:require
4 | [clojure.tools.logging :as log]
5 | [copy-trader.exchange.alpaca.driver :as driver]
6 | [copy-trader.exchange.trader :refer [compute-position-size
7 | compute-volume
8 | on-trade
9 | with-balance with-positions with-orders]]
10 | [copy-trader.util :refer [to-precision]]))
11 |
12 | (defmethod with-balance "alpaca"
13 | [trader-map]
14 | (let [balance-data (driver/alpaca-get trader-map "account")
15 | balance (-> (or (-> balance-data
16 | :equity
17 | Double/parseDouble)
18 | 0.0)
19 | (to-precision 2))]
20 | (assoc trader-map :balance-usd balance)))
21 |
22 | (defn- parse-position
23 | [trader-map position]
24 | (let [symbol-balance (compute-position-size trader-map)
25 | position-size (Math/abs (Double/parseDouble (:qty position)))
26 | position-value (Math/abs (Double/parseDouble (:market_value position)))]
27 | {:symbol (keyword (:symbol position))
28 | :balance-usd symbol-balance
29 | :position-value position-value
30 | :direction (keyword (:side position))
31 | :volume position-size
32 | :entry (Double/parseDouble (:avg_entry_price position))
33 | :type :equity}))
34 |
35 | (defmethod with-positions "alpaca"
36 | [trader-map]
37 | (let [open-positions (driver/alpaca-get trader-map "positions")
38 | reset-positions (reduce-kv
39 | (fn [m symbol position]
40 | (assoc m symbol (merge position
41 | {:direction :none
42 | :volume 0.0
43 | :entry 0.0})))
44 | {}
45 | (:open-positions trader-map))
46 | positions (->> open-positions
47 | (map #(parse-position trader-map %))
48 | (filter identity)
49 | (reduce
50 | (fn [m {:keys [symbol] :as position}]
51 | (assoc m symbol position))
52 | reset-positions))]
53 | (assoc trader-map :open-positions positions)))
54 |
55 | (defn- order->direction
56 | [order]
57 | (let [{:keys [side type]} order]
58 | (cond
59 | (and (= "buy" side) (= "limit" type)) :long
60 | (and (= "sell" side) (= "stop" type)) :long
61 | (and (= "sell" side) (= "limit" type)) :short
62 | (and (= "buy" side) (= "stop" type)) :short)))
63 |
64 | (defn- order->status
65 | [{:keys [status] :as _order}]
66 | (if (#{"accepted" "new" "partially_filled"} status)
67 | :open
68 | :closed))
69 |
70 | (defn- parse-order
71 | [order]
72 | {:symbol (keyword (:symbol order))
73 | :type (if (= "stop" (:type order))
74 | :stop-loss
75 | :limit)
76 | :direction (order->direction order)
77 | :order-id (:id order)
78 | :volume (Double/parseDouble (:qty order))
79 | :price (Double/parseDouble (if (= "stop" (:type order))
80 | (:stop_price order)
81 | (:limit_price order)))
82 | :status (order->status order)})
83 |
84 | (defmethod with-orders "alpaca"
85 | [trader-map]
86 | (let [open-orders (driver/alpaca-get trader-map "orders")]
87 | (assoc trader-map :orders (->> open-orders
88 | (mapv parse-order)))))
89 |
90 | (defn- cancel-order!
91 | [{:keys [nickname orders] :as trader-map} {:keys [symbol type direction]}]
92 | (let [open-orders (filter #(and (= symbol (name (:symbol %)))
93 | (= type (:type %))
94 | (= direction (:direction %)))
95 | orders)]
96 | (doseq [{tx-id :order-id} open-orders]
97 | (let [cancel-response (driver/alpaca-delete! trader-map (str "orders/" tx-id))]
98 | (if (<= 400 (:status cancel-response))
99 | (log/error (:body cancel-response))
100 | (log/info (format "%s: canceled %s order %s" nickname symbol tx-id)))))))
101 |
102 | ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;
103 | ;; longs ;;
104 | ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;
105 |
106 | (defmethod on-trade ["alpaca" "long" "limit"]
107 | [{nickname :nickname :as trader-map} {:keys [symbol price stop-loss]}]
108 |
109 | ;; cancel existing long order if it exists
110 | (cancel-order! trader-map {:symbol symbol :type :limit :direction :long})
111 |
112 | (let [volume (compute-volume trader-map price price stop-loss)
113 | order-data (driver/alpaca-post!
114 | trader-map "orders"
115 | {:symbol symbol
116 | :side "buy"
117 | :type "limit"
118 | :time_in_force "gtc"
119 | :limit_price price
120 | :qty (int volume)
121 | :order_class "oto"
122 | :stop_loss {:stop_price stop-loss}})]
123 | (if (<= 400 (:status order-data))
124 | (do
125 | (log/error (format "%s: failed to open long limit for %s" nickname symbol))
126 | (log/error (:body order-data)))
127 | (log/info (format "%s: longing %.2f %s at price %.2f (stop-loss at %.2f)"
128 | nickname volume symbol price stop-loss)))))
129 |
130 | (defmethod on-trade ["alpaca" "long" "take-profit"]
131 | [trader-map {:keys [symbol percentage] :as ev}]
132 | (when-let [volume (get-in trader-map [:open-positions (keyword symbol) :volume])]
133 | (let [profit-volume (int (* volume percentage))
134 | remaining-volume (- volume profit-volume)]
135 | (when (pos? profit-volume)
136 | ;; cancel existing stop-loss order if it exists
137 | (cancel-order! trader-map {:symbol symbol :type :stop-loss :direction :long})
138 |
139 | (driver/alpaca-post!
140 | trader-map "orders"
141 | {:symbol symbol
142 | :side "sell"
143 | :type "market"
144 | :time_in_force "day"
145 | :qty profit-volume})
146 | (on-trade (assoc-in trader-map
147 | [:open-positions (keyword symbol) :volume]
148 | remaining-volume)
149 | (assoc ev :order-type "stop-loss"))))))
150 |
151 | (defmethod on-trade ["alpaca" "long" "stop-loss"]
152 | [{nickname :nickname :as trader-map} {:keys [symbol stop-loss]}]
153 |
154 | ;; cancel existing stop-loss order if it exists
155 | (cancel-order! trader-map {:symbol symbol :type :stop-loss :direction :long})
156 |
157 | (let [volume (get-in trader-map [:open-positions (keyword symbol) :volume])
158 | order-data (driver/alpaca-post!
159 | trader-map "orders"
160 | {:symbol symbol
161 | :side "sell"
162 | :type "stop"
163 | :time_in_force "gtc"
164 | :stop_price stop-loss
165 | :qty (int volume)})]
166 | (if (<= 400 (:status order-data))
167 | (do
168 | (log/error (format "%s: failed to open long stop-loss for %s" nickname symbol))
169 | (log/error (:body order-data)))
170 | (log/info (format "%s: set %s long stop-loss to %.2f"
171 | nickname symbol stop-loss)))))
172 |
173 | ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;
174 | ;; shorts ;;
175 | ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;
176 |
177 | (defmethod on-trade ["alpaca" "short" "limit"]
178 | [{nickname :nickname :as trader-map} {:keys [symbol price stop-loss]}]
179 |
180 | ;; cancel existing short order if it exists
181 | (cancel-order! trader-map {:symbol symbol :type :limit :direction :short})
182 |
183 | (let [volume (compute-volume trader-map price price stop-loss)
184 | order-data (driver/alpaca-post!
185 | trader-map "orders"
186 | {:symbol symbol
187 | :side "sell"
188 | :type "limit"
189 | :time_in_force "gtc"
190 | :limit_price price
191 | :qty (int volume)
192 | :order_class "oto"
193 | :stop_loss {:stop_price stop-loss}})]
194 | (if (<= 400 (:status order-data))
195 | (do
196 | (log/error (format "%s: failed to open short limit for %s" nickname symbol))
197 | (log/error (:body order-data)))
198 | (log/info (format "%s: shorting %.2f %s at price %.2f (stop-loss at %.2f)"
199 | nickname volume symbol price stop-loss)))))
200 |
201 | (defmethod on-trade ["alpaca" "short" "take-profit"]
202 | [trader-map {:keys [symbol percentage] :as ev}]
203 | (when-let [volume (get-in trader-map [:open-positions (keyword symbol) :volume])]
204 | (let [profit-volume (int (* volume percentage))
205 | remaining-volume (- volume profit-volume)]
206 | (when (pos? profit-volume)
207 | ;; cancel existing stop-loss order if it exists
208 | (cancel-order! trader-map {:symbol symbol :type :stop-loss :direction :short})
209 |
210 | (driver/alpaca-post!
211 | trader-map "orders"
212 | {:symbol symbol
213 | :side "buy"
214 | :type "market"
215 | :time_in_force "day"
216 | :qty profit-volume})
217 | (on-trade (assoc-in trader-map
218 | [:open-positions (keyword symbol) :volume]
219 | remaining-volume)
220 | (assoc ev :order-type "stop-loss"))))))
221 |
222 | (defmethod on-trade ["alpaca" "short" "stop-loss"]
223 | [{nickname :nickname :as trader-map} {:keys [symbol stop-loss]}]
224 |
225 | ;; cancel existing stop-loss order if it exists
226 | (cancel-order! trader-map {:symbol symbol :type :stop-loss :direction :short})
227 |
228 | (let [volume (get-in trader-map [:open-positions (keyword symbol) :volume])
229 | order-data (driver/alpaca-post!
230 | trader-map "orders"
231 | {:symbol symbol
232 | :side "buy"
233 | :type "stop"
234 | :time_in_force "gtc"
235 | :stop_price stop-loss
236 | :qty (int volume)})]
237 | (if (<= 400 (:status order-data))
238 | (do
239 | (log/error (format "%s: failed to open short stop-loss for %s" nickname symbol))
240 | (log/error (:body order-data)))
241 | (log/info (format "%s: set %s short stop-loss to %.2f"
242 | nickname symbol stop-loss)))))
243 |
--------------------------------------------------------------------------------
/src/clj/copy_trader/exchange/ameritrade/trader.clj:
--------------------------------------------------------------------------------
1 | (ns copy-trader.exchange.ameritrade.trader
2 | (:refer-clojure :exclude [symbol])
3 | (:require
4 | [clojure.tools.logging :as log]
5 | [copy-trader.exchange.ameritrade.driver :as driver :refer [->account-id]]
6 | [copy-trader.exchange.trader :refer [compute-position-size
7 | compute-volume
8 | on-trade
9 | with-balance with-positions with-orders]]
10 | [copy-trader.util :refer [to-precision]]))
11 |
12 | (defn- account-for
13 | [account-id account-results]
14 | (some->> account-results
15 | (map :securitiesAccount)
16 | (filter #(= account-id (:accountId %)))
17 | first))
18 |
19 | (defmethod with-balance "ameritrade"
20 | [trader-map]
21 | (try
22 | (let [account-results (driver/ameritrade-get trader-map "accounts")
23 | account-id (->account-id trader-map)
24 | balance-data (some->> account-results
25 | (account-for account-id))
26 | cash-balance (get-in balance-data [:currentBalances :cashBalance])
27 | available-funds (get-in balance-data [:projectedBalances :availableFunds])
28 | buying-power (get-in balance-data [:projectedBalances :buyingPower])
29 | balance-usd (get-in balance-data [:currentBalances :liquidationValue])]
30 | (if (and (number? balance-usd) (pos? balance-usd))
31 | (do
32 | ;; copy the account_id down into position atoms
33 | (doseq [symbol-key (keys (:positions trader-map))]
34 | (swap! (get (:positions trader-map) symbol-key)
35 | assoc :account_id (:accountId balance-data)))
36 |
37 | (merge trader-map {:account_id (:accountId balance-data)
38 | :available-funds available-funds
39 | :buying-power buying-power
40 | :cash-balance (to-precision cash-balance 2)
41 | :balance-usd (to-precision balance-usd 2)}))
42 | trader-map))
43 | (catch Throwable t
44 | (log/error t)
45 | trader-map)))
46 |
47 | (defn- parse-position
48 | [trader-map position]
49 | ;; FIXME implement
50 | (comment
51 | {:symbol (keyword (:symbol position))
52 | :balance-usd symbol-balance
53 | :position-value position-value
54 | :direction (keyword (:side position))
55 | :volume position-size
56 | :entry (Double/parseDouble (:avg_entry_price position))
57 | :type :equity}))
58 |
59 | (defn- order->status
60 | [order]
61 | (if (#{"ACCEPTED" "PENDING_ACTIVATION" "QUEUED" "WORKING"} (:status order))
62 | :open
63 | :closed))
64 |
65 | (defn- parse-order
66 | [order]
67 | (when (#{"LIMIT" "STOP" "STOP_LIMIT"} (:orderType order))
68 | (let [is-stop-loss? (#{"STOP" "STOP_LIMIT"} (:orderType order))]
69 | {:symbol (-> order :orderLegCollection first :instrument :symbol)
70 | :type (if is-stop-loss? :stop-loss :limit)
71 | :direction (if (= "SELL" (-> order :orderLegCollection first :instruction))
72 | :long
73 | :short)
74 | :order-id (:orderId order)
75 | :volume (:quantity order)
76 | :price (if is-stop-loss?
77 | (:stopPrice order)
78 | (:price order))
79 | :status (order->status order)})))
80 |
81 | (defn- gather-orders
82 | [orders]
83 | (reduce
84 | (fn [vv order]
85 | (if-let [child-order (some-> order :childOrderStrategies first)]
86 | (concat vv [order child-order])
87 | (conj vv order)))
88 | []
89 | orders))
90 |
91 | (defmethod with-orders "ameritrade"
92 | [trader-map]
93 | (try
94 | (let [account-id (->account-id trader-map)]
95 | (if-let [orders (some->> (driver/ameritrade-get trader-map "accounts"
96 | :params {"fields" "orders"})
97 | (account-for account-id)
98 | :orderStrategies
99 | gather-orders
100 | (map parse-order)
101 | (filter identity))]
102 | (merge trader-map {:orders orders
103 | :all-orders orders})
104 | trader-map))
105 | (catch Throwable t
106 | (log/error t)
107 | trader-map)))
108 |
109 | (defn- parse-position
110 | [trader-map position]
111 | (let [trades (:latest-trades trader-map)
112 | symbol (get-in position [:instrument :symbol])
113 | opening-trade (->> trades
114 | (filter #(= symbol (:symbol %)))
115 | last)
116 | symbol-balance (compute-position-size trader-map)
117 | asset-type (-> (get-in position [:instrument :assetType])
118 | (.toLowerCase)
119 | keyword)
120 | long-quantity (:longQuantity position)
121 | is-long? (some-> long-quantity pos?)
122 | short-quantity (:shortQuantity position)
123 | is-short? (some-> short-quantity pos?)]
124 | (when (not= :cash_equivalent asset-type)
125 | (merge
126 | {:symbol symbol
127 | :balance-usd symbol-balance
128 | :position-value (* (if is-long? long-quantity short-quantity)
129 | (or (:price opening-trade)
130 | (:averagePrice position)))
131 | :direction (cond
132 | is-long? :long
133 | is-short? :short
134 | :else :none)
135 | :volume (if is-long? long-quantity short-quantity)
136 | :entry (or (:price opening-trade)
137 | (:averagePrice position)
138 | 0.0)
139 | :type asset-type}))))
140 |
141 | (defmethod with-positions "ameritrade"
142 | [trader-map]
143 | (try
144 | (let [account-id (->account-id trader-map)]
145 | (if-let [open-positions (some->> (driver/ameritrade-get trader-map "accounts"
146 | :params {"fields" "positions"})
147 | (account-for account-id)
148 | :positions
149 | not-empty)]
150 | (let [reset-positions (reduce-kv
151 | (fn [m symbol position]
152 | (assoc m symbol (merge position
153 | {:direction :none
154 | :volume 0.0
155 | :entry 0.0})))
156 | {}
157 | (:open-positions trader-map))
158 | positions (->> open-positions
159 | (map #(parse-position trader-map %))
160 | (filterv identity)
161 | (reduce
162 | (fn [m {:keys [symbol] :as position}]
163 | (assoc m symbol position))
164 | {})
165 | (merge reset-positions))]
166 | (assoc trader-map :open-positions positions))
167 | trader-map))
168 | (catch Throwable t
169 | (log/error t)
170 | trader-map)))
171 |
172 | (defn- cancel-order!
173 | [{:keys [nickname orders] :as trader-map} {:keys [symbol type direction]}]
174 | (let [open-orders (filter #(and (= symbol (name (:symbol %)))
175 | (= type (:type %))
176 | (= direction (:direction %)))
177 | orders)]
178 | (doseq [{tx-id :order-id} open-orders]
179 | (let [cancel-response (driver/ameritrade-delete!
180 | trader-map
181 | (format "accounts/%s/orders/%s"
182 | (->account-id trader-map)
183 | tx-id))]
184 | (if (<= 400 (:status cancel-response))
185 | (log/error (:body cancel-response))
186 | (log/info (format "%s: canceled %s order %s" nickname symbol tx-id)))))))
187 |
188 | ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;
189 | ;; longs ;;
190 | ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;
191 |
192 | (defmethod on-trade ["ameritrade" "long" "limit"]
193 | [{nickname :nickname :as trader-map} {:keys [symbol price stop-loss]}]
194 |
195 | ;; cancel existing long order if it exists
196 | (cancel-order! trader-map {:symbol symbol :type :limit :direction :long})
197 |
198 | (let [volume (compute-volume trader-map price price stop-loss)
199 | instrument {:symbol symbol
200 | :assetType "EQUITY"}
201 | order-data (driver/ameritrade-post!
202 | trader-map
203 | (format "accounts/%s/orders" (->account-id trader-map))
204 | {:session "NORMAL"
205 | :orderType "LIMIT"
206 | :duration "GOOD_TILL_CANCEL"
207 | :price price
208 | :orderStrategyType "TRIGGER"
209 | :orderLegCollection [{:instruction "BUY"
210 | :quantity (int volume)
211 | :instrument instrument}]
212 | :childOrderStrategies [{:session "NORMAL"
213 | :orderType "STOP"
214 | :stopPrice stop-loss
215 | :duration "GOOD_TILL_CANCEL"
216 | :orderStrategyType "SINGLE"
217 | :orderLegCollection [{:instruction "SELL"
218 | :quantity (int volume)
219 | :instrument instrument}]}]})]
220 | (if (<= 400 (:status order-data))
221 | (do
222 | (log/error (format "%s: failed to open long limit for %s" nickname symbol))
223 | (log/error (:body order-data)))
224 | (log/info (format "%s: longing %.2f %s at price %.2f (stop-loss at %.2f)"
225 | nickname volume symbol price stop-loss)))))
226 |
227 | (defmethod on-trade ["ameritrade" "long" "take-profit"]
228 | [trader-map {:keys [symbol percentage] :as ev}]
229 | (when-let [volume (get-in trader-map [:open-positions (keyword symbol) :volume])]
230 | (let [profit-volume (int (* volume percentage))
231 | remaining-volume (- volume profit-volume)
232 | instrument {:symbol symbol
233 | :assetType "EQUITY"}]
234 | (when (pos? profit-volume)
235 | ;; cancel existing stop-loss order if it exists
236 | (cancel-order! trader-map {:symbol symbol :type :stop-loss :direction :long})
237 |
238 | (driver/ameritrade-post!
239 | trader-map
240 | (format "accounts/%s/orders" (->account-id trader-map))
241 | {:session "NORMAL"
242 | :orderType "MARKET"
243 | :duration "DAY"
244 | :orderStrategyType "SINGLE"
245 | :orderLegCollection [{:instruction "SELL"
246 | :quantity (int volume)
247 | :instrument instrument}]})
248 | (on-trade (assoc-in trader-map
249 | [:open-positions (keyword symbol) :volume]
250 | remaining-volume)
251 | (assoc ev :order-type "stop-loss"))))))
252 |
253 | (defmethod on-trade ["ameritrade" "long" "stop-loss"]
254 | [{nickname :nickname :as trader-map} {:keys [symbol stop-loss]}]
255 |
256 | ;; cancel existing stop-loss order if it exists
257 | (cancel-order! trader-map {:symbol symbol :type :stop-loss :direction :long})
258 |
259 | (let [volume (get-in trader-map [:open-positions (keyword symbol) :volume])
260 | instrument {:symbol symbol
261 | :assetType "EQUITY"}
262 | order-data (driver/ameritrade-post!
263 | trader-map
264 | (format "accounts/%s/orders" (->account-id trader-map))
265 | {:session "NORMAL"
266 | :orderType "STOP"
267 | :duration "GOOD_TILL_CANCEL"
268 | :stopPrice stop-loss
269 | :orderStrategyType "SINGLE"
270 | :orderLegCollection [{:instruction "SELL"
271 | :quantity (int volume)
272 | :instrument instrument}]})]
273 | (if (<= 400 (:status order-data))
274 | (do
275 | (log/error (format "%s: failed to open long stop-loss for %s" nickname symbol))
276 | (log/error (:body order-data)))
277 | (log/info (format "%s: set %s long stop-loss to %.2f"
278 | nickname symbol stop-loss)))))
279 |
280 | ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;
281 | ;; shorts ;;
282 | ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;
283 |
284 | (defmethod on-trade ["ameritrade" "short" "limit"]
285 | [{nickname :nickname :as trader-map} {:keys [symbol price stop-loss]}]
286 |
287 | ;; cancel existing short order if it exists
288 | (cancel-order! trader-map {:symbol symbol :type :limit :direction :short})
289 |
290 | (let [volume (compute-volume trader-map price price stop-loss)
291 | instrument {:symbol symbol
292 | :assetType "EQUITY"}
293 | order-data (driver/ameritrade-post!
294 | trader-map
295 | (format "accounts/%s/orders" (->account-id trader-map))
296 | {:session "NORMAL"
297 | :orderType "LIMIT"
298 | :duration "GOOD_TILL_CANCEL"
299 | :price price
300 | :orderStrategyType "TRIGGER"
301 | :orderLegCollection [{:instruction "SELL_SHORT"
302 | :quantity (int volume)
303 | :instrument instrument}]
304 | :childOrderStrategies [{:session "NORMAL"
305 | :orderType "STOP"
306 | :stopPrice stop-loss
307 | :duration "GOOD_TILL_CANCEL"
308 | :orderStrategyType "SINGLE"
309 | :orderLegCollection [{:instruction "BUY_TO_COVER"
310 | :quantity (int volume)
311 | :instrument instrument}]}]})]
312 | (if (<= 400 (:status order-data))
313 | (do
314 | (log/error (format "%s: failed to open short limit for %s" nickname symbol))
315 | (log/error (:body order-data)))
316 | (log/info (format "%s: shorting %.2f %s at price %.2f (stop-loss at %.2f)"
317 | nickname volume symbol price stop-loss)))))
318 |
319 | (defmethod on-trade ["ameritrade" "short" "take-profit"]
320 | [trader-map {:keys [symbol percentage] :as ev}]
321 | (when-let [volume (get-in trader-map [:open-positions (keyword symbol) :volume])]
322 | (let [profit-volume (int (* volume percentage))
323 | remaining-volume (- volume profit-volume)
324 | instrument {:symbol symbol
325 | :assetType "EQUITY"}]
326 | (when (pos? profit-volume)
327 | ;; cancel existing stop-loss order if it exists
328 | (cancel-order! trader-map {:symbol symbol :type :stop-loss :direction :short})
329 |
330 | (driver/ameritrade-post!
331 | trader-map
332 | (format "accounts/%s/orders" (->account-id trader-map))
333 | {:session "NORMAL"
334 | :orderType "MARKET"
335 | :duration "DAY"
336 | :orderStrategyType "SINGLE"
337 | :orderLegCollection [{:instruction "BUY_TO_COVER"
338 | :quantity (int volume)
339 | :instrument instrument}]})
340 | (on-trade (assoc-in trader-map
341 | [:open-positions (keyword symbol) :volume]
342 | remaining-volume)
343 | (assoc ev :order-type "stop-loss"))))))
344 |
345 | (defmethod on-trade ["ameritrade" "short" "stop-loss"]
346 | [{nickname :nickname :as trader-map} {:keys [symbol stop-loss]}]
347 |
348 | ;; cancel existing stop-loss order if it exists
349 | (cancel-order! trader-map {:symbol symbol :type :stop-loss :direction :short})
350 |
351 | (let [volume (get-in trader-map [:open-positions (keyword symbol) :volume])
352 | instrument {:symbol symbol
353 | :assetType "EQUITY"}
354 | order-data (driver/ameritrade-post!
355 | trader-map
356 | (format "accounts/%s/orders" (->account-id trader-map))
357 | {:session "NORMAL"
358 | :orderType "STOP"
359 | :duration "GOOD_TILL_CANCEL"
360 | :stopPrice stop-loss
361 | :orderStrategyType "SINGLE"
362 | :orderLegCollection [{:instruction "BUY_TO_COVER"
363 | :quantity (int volume)
364 | :instrument instrument}]})]
365 | (if (<= 400 (:status order-data))
366 | (do
367 | (log/error (format "%s: failed to open short stop-loss for %s" nickname symbol))
368 | (log/error (:body order-data)))
369 | (log/info (format "%s: set %s short stop-loss to %.2f"
370 | nickname symbol stop-loss)))))
371 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 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 Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 |
633 | Copyright (C)
634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published
637 | by the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------