going somewhere
This commit is contained in:
1 parent
b972e6e69f
commit
10b054db25
31 files changed
+617
-298
No files matched your search
@@ -39,6 +39,7 @@ file should be self documented.
|
||||
* [clojure](https://clojure.org/) for programming language
|
||||
* [kafka](http://kafka.apache.org/) for message broker/database
|
||||
* [datomic-free](https://www.datomic.com/) for transaction needs
|
||||
* [clickhouse](https://clickhouse.yandex) to store the market data
|
||||
|
||||
#### In the browser
|
||||
* [clojure script](https://clojurescript.org/) for programming language
|
||||
|
||||
@@ -159,6 +159,14 @@ services:
|
||||
- "4335:4335"
|
||||
- "4336:4336"
|
||||
|
||||
clickhouse:
|
||||
image: "yandex/clickhouse-server:18.14"
|
||||
restart: "unless-stopped"
|
||||
ports:
|
||||
- "8123:8123"
|
||||
- "9000:9000"
|
||||
|
||||
|
||||
|
||||
# skirmish itself
|
||||
|
||||
|
||||
+2
-4
@@ -1,7 +1,6 @@
|
||||
(ns skirmish.wui.middleware
|
||||
(:require [ring.middleware.defaults :refer [site-defaults wrap-defaults]]
|
||||
[prone.middleware :refer [wrap-exceptions]]
|
||||
[ring.middleware.reload :refer [wrap-reload]]))
|
||||
[prone.middleware :refer [wrap-exceptions]]))
|
||||
|
||||
(defn custom-dev-middleware [handler]
|
||||
(fn [request]
|
||||
@@ -12,5 +11,4 @@
|
||||
(-> handler
|
||||
(wrap-defaults site-defaults)
|
||||
custom-dev-middleware
|
||||
wrap-exceptions
|
||||
wrap-reload))
|
||||
wrap-exceptions))
|
||||
Generated
+3
-3
@@ -5,9 +5,9 @@
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"bulma": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.7.1.tgz",
|
||||
"integrity": "sha512-wRSO2LXB+qI9Pyz2id+uZr4quz5aftSN7Ay1ysr1+krzVp3utD+Ci4CeKuZdrYGc800t65b7heXBL6qw2Wo/lQ=="
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.7.2.tgz",
|
||||
"integrity": "sha512-6JHEu8U/1xsyOst/El5ImLcZIiE2JFXgvrz8GGWbnDLwTNRPJzdAM0aoUM1Ns0avALcVb6KZz9NhzmU53dGDcQ=="
|
||||
},
|
||||
"font-awesome": {
|
||||
"version": "4.7.0",
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@
|
||||
|
||||
:java-source-paths ["src/java"]
|
||||
|
||||
:npm {:dependencies [[bulma "0.7.1"]
|
||||
:npm {:dependencies [[bulma "0.7.2"]
|
||||
[font-awesome "4.7.0"]]}
|
||||
|
||||
:ring {:handler skirmish.wui.handler/app
|
||||
|
||||
@@ -38,14 +38,10 @@
|
||||
:ip "0.0.0.0"
|
||||
:max-body 524288 ;; 512kb, small but enough
|
||||
:max-ws 524288
|
||||
:default-sec-id :dapm
|
||||
:datomic {:transactor-url "datomic:free://localhost:4334/skirmish.broker"}}
|
||||
|
||||
:wui
|
||||
{:port #long #or [#env WUI_PORT 3000]}
|
||||
|
||||
;; stupid parameters about benchmarking tools
|
||||
:benchmark {
|
||||
:number-of-requests-per-securities 30}}
|
||||
|
||||
{:port #long #or [#env WUI_PORT 3000]}}
|
||||
|
||||
|
||||
+378
-51
@@ -14,7 +14,7 @@
|
||||
-ms-user-select: none;
|
||||
user-select: none; }
|
||||
|
||||
.select:not(.is-multiple):not(.is-loading)::after, .navbar-link::after {
|
||||
.select:not(.is-multiple):not(.is-loading)::after, .navbar-link:not(.is-arrowless)::after {
|
||||
border: 3px solid transparent;
|
||||
border-radius: 2px;
|
||||
border-right: 0;
|
||||
@@ -31,7 +31,7 @@
|
||||
width: 0.625em; }
|
||||
|
||||
.box:not(:last-child), .content:not(:last-child), .notification:not(:last-child), .progress:not(:last-child), .table:not(:last-child), .table-container:not(:last-child), .title:not(:last-child),
|
||||
.subtitle:not(:last-child), .block:not(:last-child), .highlight:not(:last-child), .breadcrumb:not(:last-child), .level:not(:last-child), .message:not(:last-child), .tabs:not(:last-child) {
|
||||
.subtitle:not(:last-child), .block:not(:last-child), .highlight:not(:last-child), .breadcrumb:not(:last-child), .level:not(:last-child), .list:not(:last-child), .message:not(:last-child), .tabs:not(:last-child) {
|
||||
margin-bottom: 1.5rem; }
|
||||
|
||||
.delete, .modal-close {
|
||||
@@ -41,6 +41,7 @@
|
||||
border: none;
|
||||
border-radius: 290486px;
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
display: inline-block;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
@@ -993,6 +994,16 @@ a.has-text-danger:hover, a.has-text-danger:focus {
|
||||
.is-hidden {
|
||||
display: none !important; }
|
||||
|
||||
.is-sr-only {
|
||||
border: none !important;
|
||||
clip: rect(0, 0, 0, 0) !important;
|
||||
height: 0.01em !important;
|
||||
overflow: hidden !important;
|
||||
padding: 0 !important;
|
||||
position: absolute !important;
|
||||
white-space: nowrap !important;
|
||||
width: 0.01em !important; }
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.is-hidden-mobile {
|
||||
display: none !important; } }
|
||||
@@ -1081,10 +1092,10 @@ a.has-text-danger:hover, a.has-text-danger:focus {
|
||||
box-shadow: none !important; }
|
||||
|
||||
.box {
|
||||
background-color: white;
|
||||
background-color: #7a7a7a;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
|
||||
color: whitesmoke;
|
||||
box-shadow: 0 2px 3px #b5b5b5, 0 0 0 1px #b5b5b5;
|
||||
color: white;
|
||||
display: block;
|
||||
padding: 1.25rem; }
|
||||
|
||||
@@ -1788,7 +1799,7 @@ a.box:active {
|
||||
justify-content: flex-start; }
|
||||
.buttons .button {
|
||||
margin-bottom: 0.5rem; }
|
||||
.buttons .button:not(:last-child) {
|
||||
.buttons .button:not(:last-child):not(.is-fullwidth) {
|
||||
margin-right: 0.5rem; }
|
||||
.buttons:last-child {
|
||||
margin-bottom: -0.5rem; }
|
||||
@@ -1903,9 +1914,19 @@ a.box:active {
|
||||
padding: 1.25em 1.5em; }
|
||||
|
||||
.content ol {
|
||||
list-style: decimal outside;
|
||||
list-style-position: outside;
|
||||
margin-left: 2em;
|
||||
margin-top: 1em; }
|
||||
.content ol:not([type]) {
|
||||
list-style-type: decimal; }
|
||||
.content ol:not([type]).is-lower-alpha {
|
||||
list-style-type: lower-alpha; }
|
||||
.content ol:not([type]).is-lower-roman {
|
||||
list-style-type: lower-roman; }
|
||||
.content ol:not([type]).is-upper-alpha {
|
||||
list-style-type: upper-alpha; }
|
||||
.content ol:not([type]).is-upper-roman {
|
||||
list-style-type: upper-roman; }
|
||||
|
||||
.content ul {
|
||||
list-style: disc outside;
|
||||
@@ -2239,7 +2260,7 @@ a.box:active {
|
||||
.select select:not([multiple]) {
|
||||
padding-right: 2.5em; }
|
||||
.select select[multiple] {
|
||||
height: initial;
|
||||
height: auto;
|
||||
padding: 0; }
|
||||
.select select[multiple] option {
|
||||
padding: 0.5em 1em; }
|
||||
@@ -2595,12 +2616,13 @@ a.box:active {
|
||||
border-color: #cfcfcf; }
|
||||
|
||||
.file-input {
|
||||
height: 0.01em;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
outline: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 0.01em; }
|
||||
width: 100%; }
|
||||
|
||||
.file-cta,
|
||||
.file-name {
|
||||
@@ -2696,31 +2718,31 @@ a.box:active {
|
||||
.field.has-addons .control:last-child .select select {
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-left-radius: 0; }
|
||||
.field.has-addons .control .button:hover, .field.has-addons .control .button.is-hovered,
|
||||
.field.has-addons .control .input:hover,
|
||||
.field.has-addons .control .input.is-hovered,
|
||||
.field.has-addons .control .select select:hover,
|
||||
.field.has-addons .control .select select.is-hovered {
|
||||
.field.has-addons .control .button:not([disabled]):hover, .field.has-addons .control .button:not([disabled]).is-hovered,
|
||||
.field.has-addons .control .input:not([disabled]):hover,
|
||||
.field.has-addons .control .input:not([disabled]).is-hovered,
|
||||
.field.has-addons .control .select select:not([disabled]):hover,
|
||||
.field.has-addons .control .select select:not([disabled]).is-hovered {
|
||||
z-index: 2; }
|
||||
.field.has-addons .control .button:focus, .field.has-addons .control .button.is-focused, .field.has-addons .control .button:active, .field.has-addons .control .button.is-active,
|
||||
.field.has-addons .control .input:focus,
|
||||
.field.has-addons .control .input.is-focused,
|
||||
.field.has-addons .control .input:active,
|
||||
.field.has-addons .control .input.is-active,
|
||||
.field.has-addons .control .select select:focus,
|
||||
.field.has-addons .control .select select.is-focused,
|
||||
.field.has-addons .control .select select:active,
|
||||
.field.has-addons .control .select select.is-active {
|
||||
.field.has-addons .control .button:not([disabled]):focus, .field.has-addons .control .button:not([disabled]).is-focused, .field.has-addons .control .button:not([disabled]):active, .field.has-addons .control .button:not([disabled]).is-active,
|
||||
.field.has-addons .control .input:not([disabled]):focus,
|
||||
.field.has-addons .control .input:not([disabled]).is-focused,
|
||||
.field.has-addons .control .input:not([disabled]):active,
|
||||
.field.has-addons .control .input:not([disabled]).is-active,
|
||||
.field.has-addons .control .select select:not([disabled]):focus,
|
||||
.field.has-addons .control .select select:not([disabled]).is-focused,
|
||||
.field.has-addons .control .select select:not([disabled]):active,
|
||||
.field.has-addons .control .select select:not([disabled]).is-active {
|
||||
z-index: 3; }
|
||||
.field.has-addons .control .button:focus:hover, .field.has-addons .control .button.is-focused:hover, .field.has-addons .control .button:active:hover, .field.has-addons .control .button.is-active:hover,
|
||||
.field.has-addons .control .input:focus:hover,
|
||||
.field.has-addons .control .input.is-focused:hover,
|
||||
.field.has-addons .control .input:active:hover,
|
||||
.field.has-addons .control .input.is-active:hover,
|
||||
.field.has-addons .control .select select:focus:hover,
|
||||
.field.has-addons .control .select select.is-focused:hover,
|
||||
.field.has-addons .control .select select:active:hover,
|
||||
.field.has-addons .control .select select.is-active:hover {
|
||||
.field.has-addons .control .button:not([disabled]):focus:hover, .field.has-addons .control .button:not([disabled]).is-focused:hover, .field.has-addons .control .button:not([disabled]):active:hover, .field.has-addons .control .button:not([disabled]).is-active:hover,
|
||||
.field.has-addons .control .input:not([disabled]):focus:hover,
|
||||
.field.has-addons .control .input:not([disabled]).is-focused:hover,
|
||||
.field.has-addons .control .input:not([disabled]):active:hover,
|
||||
.field.has-addons .control .input:not([disabled]).is-active:hover,
|
||||
.field.has-addons .control .select select:not([disabled]):focus:hover,
|
||||
.field.has-addons .control .select select:not([disabled]).is-focused:hover,
|
||||
.field.has-addons .control .select select:not([disabled]):active:hover,
|
||||
.field.has-addons .control .select select:not([disabled]).is-active:hover {
|
||||
z-index: 4; }
|
||||
.field.has-addons .control.is-expanded {
|
||||
flex-grow: 1; }
|
||||
@@ -2805,6 +2827,7 @@ a.box:active {
|
||||
margin-right: 0.75rem; } }
|
||||
|
||||
.control {
|
||||
clear: both;
|
||||
font-size: 1rem;
|
||||
position: relative;
|
||||
text-align: left; }
|
||||
@@ -2963,7 +2986,7 @@ a.box:active {
|
||||
border-radius: 4px;
|
||||
padding: 1.25rem 2.5rem 1.25rem 1.5rem;
|
||||
position: relative; }
|
||||
.notification a:not(.button) {
|
||||
.notification a:not(.button):not(.dropdown-item) {
|
||||
color: currentColor;
|
||||
text-decoration: underline; }
|
||||
.notification strong {
|
||||
@@ -3205,6 +3228,8 @@ a.box:active {
|
||||
.table.is-hoverable tbody tr:not(.is-selected):hover {
|
||||
background-color: #242424; }
|
||||
.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover {
|
||||
background-color: #242424; }
|
||||
.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even) {
|
||||
background-color: whitesmoke; }
|
||||
.table.is-narrow td,
|
||||
.table.is-narrow th {
|
||||
@@ -3502,7 +3527,7 @@ a.tag:hover {
|
||||
position: relative; }
|
||||
|
||||
.card-header {
|
||||
background-color: none;
|
||||
background-color: transparent;
|
||||
align-items: stretch;
|
||||
box-shadow: 0 1px 2px rgba(10, 10, 10, 0.1);
|
||||
display: flex; }
|
||||
@@ -3529,11 +3554,11 @@ a.tag:hover {
|
||||
position: relative; }
|
||||
|
||||
.card-content {
|
||||
background-color: none;
|
||||
background-color: transparent;
|
||||
padding: 1.5rem; }
|
||||
|
||||
.card-footer {
|
||||
background-color: none;
|
||||
background-color: transparent;
|
||||
border-top: 1px solid #dbdbdb;
|
||||
align-items: stretch;
|
||||
display: flex; }
|
||||
@@ -3591,13 +3616,18 @@ a.tag:hover {
|
||||
padding: 0.375rem 1rem;
|
||||
position: relative; }
|
||||
|
||||
a.dropdown-item {
|
||||
a.dropdown-item,
|
||||
button.dropdown-item {
|
||||
padding-right: 3rem;
|
||||
white-space: nowrap; }
|
||||
a.dropdown-item:hover {
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
width: 100%; }
|
||||
a.dropdown-item:hover,
|
||||
button.dropdown-item:hover {
|
||||
background-color: #0a0a0a;
|
||||
color: #0a0a0a; }
|
||||
a.dropdown-item.is-active {
|
||||
a.dropdown-item.is-active,
|
||||
button.dropdown-item.is-active {
|
||||
background-color: #00d1b2;
|
||||
color: #fff; }
|
||||
|
||||
@@ -3623,10 +3653,9 @@ a.dropdown-item {
|
||||
display: flex; }
|
||||
.level.is-mobile .level-left + .level-right {
|
||||
margin-top: 0; }
|
||||
.level.is-mobile .level-item {
|
||||
margin-right: 0.75rem; }
|
||||
.level.is-mobile .level-item:not(:last-child) {
|
||||
margin-bottom: 0; }
|
||||
margin-bottom: 0;
|
||||
margin-right: 0.75rem; }
|
||||
.level.is-mobile .level-item:not(.is-narrow) {
|
||||
flex-grow: 1; }
|
||||
@media screen and (min-width: 769px), print {
|
||||
@@ -3679,6 +3708,32 @@ a.dropdown-item {
|
||||
.level-right {
|
||||
display: flex; } }
|
||||
|
||||
.list {
|
||||
background-color: white;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1); }
|
||||
|
||||
.list-item {
|
||||
display: block;
|
||||
padding: 0.5em 1em; }
|
||||
.list-item:not(a) {
|
||||
color: whitesmoke; }
|
||||
.list-item:first-child {
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px; }
|
||||
.list-item:last-child {
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px; }
|
||||
.list-item:not(:last-child) {
|
||||
border-bottom: 1px solid #dbdbdb; }
|
||||
.list-item.is-active {
|
||||
background-color: #00d1b2;
|
||||
color: #fff; }
|
||||
|
||||
a.list-item {
|
||||
background-color: #0a0a0a;
|
||||
cursor: pointer; }
|
||||
|
||||
.media {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
@@ -3722,6 +3777,10 @@ a.dropdown-item {
|
||||
flex-shrink: 1;
|
||||
text-align: left; }
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.media-content {
|
||||
overflow-x: auto; } }
|
||||
|
||||
.menu {
|
||||
font-size: 1rem; }
|
||||
.menu.is-small {
|
||||
@@ -3891,6 +3950,7 @@ a.dropdown-item {
|
||||
.modal {
|
||||
align-items: center;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
@@ -3927,7 +3987,8 @@ a.dropdown-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: calc(100vh - 40px);
|
||||
overflow: hidden; }
|
||||
overflow: hidden;
|
||||
-ms-overflow-y: visible; }
|
||||
|
||||
.modal-card-head,
|
||||
.modal-card-foot {
|
||||
@@ -3984,6 +4045,8 @@ a.dropdown-item {
|
||||
color: #0a0a0a; }
|
||||
.navbar.is-white .navbar-brand .navbar-link::after {
|
||||
border-color: #0a0a0a; }
|
||||
.navbar.is-white .navbar-burger {
|
||||
color: #0a0a0a; }
|
||||
@media screen and (min-width: 1088px) {
|
||||
.navbar.is-white .navbar-start > .navbar-item,
|
||||
.navbar.is-white .navbar-start .navbar-link,
|
||||
@@ -4022,6 +4085,8 @@ a.dropdown-item {
|
||||
color: white; }
|
||||
.navbar.is-black .navbar-brand .navbar-link::after {
|
||||
border-color: white; }
|
||||
.navbar.is-black .navbar-burger {
|
||||
color: white; }
|
||||
@media screen and (min-width: 1088px) {
|
||||
.navbar.is-black .navbar-start > .navbar-item,
|
||||
.navbar.is-black .navbar-start .navbar-link,
|
||||
@@ -4060,6 +4125,8 @@ a.dropdown-item {
|
||||
color: #363636; }
|
||||
.navbar.is-light .navbar-brand .navbar-link::after {
|
||||
border-color: #363636; }
|
||||
.navbar.is-light .navbar-burger {
|
||||
color: #363636; }
|
||||
@media screen and (min-width: 1088px) {
|
||||
.navbar.is-light .navbar-start > .navbar-item,
|
||||
.navbar.is-light .navbar-start .navbar-link,
|
||||
@@ -4098,6 +4165,8 @@ a.dropdown-item {
|
||||
color: whitesmoke; }
|
||||
.navbar.is-dark .navbar-brand .navbar-link::after {
|
||||
border-color: whitesmoke; }
|
||||
.navbar.is-dark .navbar-burger {
|
||||
color: whitesmoke; }
|
||||
@media screen and (min-width: 1088px) {
|
||||
.navbar.is-dark .navbar-start > .navbar-item,
|
||||
.navbar.is-dark .navbar-start .navbar-link,
|
||||
@@ -4136,6 +4205,8 @@ a.dropdown-item {
|
||||
color: #fff; }
|
||||
.navbar.is-primary .navbar-brand .navbar-link::after {
|
||||
border-color: #fff; }
|
||||
.navbar.is-primary .navbar-burger {
|
||||
color: #fff; }
|
||||
@media screen and (min-width: 1088px) {
|
||||
.navbar.is-primary .navbar-start > .navbar-item,
|
||||
.navbar.is-primary .navbar-start .navbar-link,
|
||||
@@ -4174,6 +4245,8 @@ a.dropdown-item {
|
||||
color: #fff; }
|
||||
.navbar.is-link .navbar-brand .navbar-link::after {
|
||||
border-color: #fff; }
|
||||
.navbar.is-link .navbar-burger {
|
||||
color: #fff; }
|
||||
@media screen and (min-width: 1088px) {
|
||||
.navbar.is-link .navbar-start > .navbar-item,
|
||||
.navbar.is-link .navbar-start .navbar-link,
|
||||
@@ -4212,6 +4285,8 @@ a.dropdown-item {
|
||||
color: #fff; }
|
||||
.navbar.is-info .navbar-brand .navbar-link::after {
|
||||
border-color: #fff; }
|
||||
.navbar.is-info .navbar-burger {
|
||||
color: #fff; }
|
||||
@media screen and (min-width: 1088px) {
|
||||
.navbar.is-info .navbar-start > .navbar-item,
|
||||
.navbar.is-info .navbar-start .navbar-link,
|
||||
@@ -4250,6 +4325,8 @@ a.dropdown-item {
|
||||
color: #fff; }
|
||||
.navbar.is-success .navbar-brand .navbar-link::after {
|
||||
border-color: #fff; }
|
||||
.navbar.is-success .navbar-burger {
|
||||
color: #fff; }
|
||||
@media screen and (min-width: 1088px) {
|
||||
.navbar.is-success .navbar-start > .navbar-item,
|
||||
.navbar.is-success .navbar-start .navbar-link,
|
||||
@@ -4288,6 +4365,8 @@ a.dropdown-item {
|
||||
color: rgba(0, 0, 0, 0.7); }
|
||||
.navbar.is-warning .navbar-brand .navbar-link::after {
|
||||
border-color: rgba(0, 0, 0, 0.7); }
|
||||
.navbar.is-warning .navbar-burger {
|
||||
color: rgba(0, 0, 0, 0.7); }
|
||||
@media screen and (min-width: 1088px) {
|
||||
.navbar.is-warning .navbar-start > .navbar-item,
|
||||
.navbar.is-warning .navbar-start .navbar-link,
|
||||
@@ -4326,6 +4405,8 @@ a.dropdown-item {
|
||||
color: #fff; }
|
||||
.navbar.is-danger .navbar-brand .navbar-link::after {
|
||||
border-color: #fff; }
|
||||
.navbar.is-danger .navbar-burger {
|
||||
color: #fff; }
|
||||
@media screen and (min-width: 1088px) {
|
||||
.navbar.is-danger .navbar-start > .navbar-item,
|
||||
.navbar.is-danger .navbar-start .navbar-link,
|
||||
@@ -4395,6 +4476,7 @@ body.has-navbar-fixed-bottom {
|
||||
overflow-y: hidden; }
|
||||
|
||||
.navbar-burger {
|
||||
color: #4a4a4a;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
height: 3.25rem;
|
||||
@@ -4481,9 +4563,9 @@ a.navbar-item,
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1; }
|
||||
|
||||
.navbar-link {
|
||||
.navbar-link:not(.is-arrowless) {
|
||||
padding-right: 2.5em; }
|
||||
.navbar-link::after {
|
||||
.navbar-link:not(.is-arrowless)::after {
|
||||
border-color: #00d1b2;
|
||||
margin-top: -0.375em;
|
||||
right: 1.125em; }
|
||||
@@ -4645,10 +4727,10 @@ a.navbar-item,
|
||||
display: block; }
|
||||
.navbar > .container .navbar-brand,
|
||||
.container > .navbar .navbar-brand {
|
||||
margin-left: -1rem; }
|
||||
margin-left: -.75rem; }
|
||||
.navbar > .container .navbar-menu,
|
||||
.container > .navbar .navbar-menu {
|
||||
margin-right: -1rem; }
|
||||
margin-right: -.75rem; }
|
||||
.navbar.is-fixed-bottom-desktop, .navbar.is-fixed-top-desktop {
|
||||
left: 0;
|
||||
position: fixed;
|
||||
@@ -5816,22 +5898,265 @@ label.panel-block {
|
||||
padding-right: var(--columnGap); }
|
||||
.columns.is-variable.is-0 {
|
||||
--columnGap: 0rem; }
|
||||
@media screen and (max-width: 768px) {
|
||||
.columns.is-variable.is-0-mobile {
|
||||
--columnGap: 0rem; } }
|
||||
@media screen and (min-width: 769px), print {
|
||||
.columns.is-variable.is-0-tablet {
|
||||
--columnGap: 0rem; } }
|
||||
@media screen and (min-width: 769px) and (max-width: 1087px) {
|
||||
.columns.is-variable.is-0-tablet-only {
|
||||
--columnGap: 0rem; } }
|
||||
@media screen and (max-width: 1087px) {
|
||||
.columns.is-variable.is-0-touch {
|
||||
--columnGap: 0rem; } }
|
||||
@media screen and (min-width: 1088px) {
|
||||
.columns.is-variable.is-0-desktop {
|
||||
--columnGap: 0rem; } }
|
||||
@media screen and (min-width: 1088px) and (max-width: 1279px) {
|
||||
.columns.is-variable.is-0-desktop-only {
|
||||
--columnGap: 0rem; } }
|
||||
@media screen and (min-width: 1280px) {
|
||||
.columns.is-variable.is-0-widescreen {
|
||||
--columnGap: 0rem; } }
|
||||
@media screen and (min-width: 1280px) and (max-width: 1471px) {
|
||||
.columns.is-variable.is-0-widescreen-only {
|
||||
--columnGap: 0rem; } }
|
||||
@media screen and (min-width: 1472px) {
|
||||
.columns.is-variable.is-0-fullhd {
|
||||
--columnGap: 0rem; } }
|
||||
.columns.is-variable.is-1 {
|
||||
--columnGap: 0.25rem; }
|
||||
@media screen and (max-width: 768px) {
|
||||
.columns.is-variable.is-1-mobile {
|
||||
--columnGap: 0.25rem; } }
|
||||
@media screen and (min-width: 769px), print {
|
||||
.columns.is-variable.is-1-tablet {
|
||||
--columnGap: 0.25rem; } }
|
||||
@media screen and (min-width: 769px) and (max-width: 1087px) {
|
||||
.columns.is-variable.is-1-tablet-only {
|
||||
--columnGap: 0.25rem; } }
|
||||
@media screen and (max-width: 1087px) {
|
||||
.columns.is-variable.is-1-touch {
|
||||
--columnGap: 0.25rem; } }
|
||||
@media screen and (min-width: 1088px) {
|
||||
.columns.is-variable.is-1-desktop {
|
||||
--columnGap: 0.25rem; } }
|
||||
@media screen and (min-width: 1088px) and (max-width: 1279px) {
|
||||
.columns.is-variable.is-1-desktop-only {
|
||||
--columnGap: 0.25rem; } }
|
||||
@media screen and (min-width: 1280px) {
|
||||
.columns.is-variable.is-1-widescreen {
|
||||
--columnGap: 0.25rem; } }
|
||||
@media screen and (min-width: 1280px) and (max-width: 1471px) {
|
||||
.columns.is-variable.is-1-widescreen-only {
|
||||
--columnGap: 0.25rem; } }
|
||||
@media screen and (min-width: 1472px) {
|
||||
.columns.is-variable.is-1-fullhd {
|
||||
--columnGap: 0.25rem; } }
|
||||
.columns.is-variable.is-2 {
|
||||
--columnGap: 0.5rem; }
|
||||
@media screen and (max-width: 768px) {
|
||||
.columns.is-variable.is-2-mobile {
|
||||
--columnGap: 0.5rem; } }
|
||||
@media screen and (min-width: 769px), print {
|
||||
.columns.is-variable.is-2-tablet {
|
||||
--columnGap: 0.5rem; } }
|
||||
@media screen and (min-width: 769px) and (max-width: 1087px) {
|
||||
.columns.is-variable.is-2-tablet-only {
|
||||
--columnGap: 0.5rem; } }
|
||||
@media screen and (max-width: 1087px) {
|
||||
.columns.is-variable.is-2-touch {
|
||||
--columnGap: 0.5rem; } }
|
||||
@media screen and (min-width: 1088px) {
|
||||
.columns.is-variable.is-2-desktop {
|
||||
--columnGap: 0.5rem; } }
|
||||
@media screen and (min-width: 1088px) and (max-width: 1279px) {
|
||||
.columns.is-variable.is-2-desktop-only {
|
||||
--columnGap: 0.5rem; } }
|
||||
@media screen and (min-width: 1280px) {
|
||||
.columns.is-variable.is-2-widescreen {
|
||||
--columnGap: 0.5rem; } }
|
||||
@media screen and (min-width: 1280px) and (max-width: 1471px) {
|
||||
.columns.is-variable.is-2-widescreen-only {
|
||||
--columnGap: 0.5rem; } }
|
||||
@media screen and (min-width: 1472px) {
|
||||
.columns.is-variable.is-2-fullhd {
|
||||
--columnGap: 0.5rem; } }
|
||||
.columns.is-variable.is-3 {
|
||||
--columnGap: 0.75rem; }
|
||||
@media screen and (max-width: 768px) {
|
||||
.columns.is-variable.is-3-mobile {
|
||||
--columnGap: 0.75rem; } }
|
||||
@media screen and (min-width: 769px), print {
|
||||
.columns.is-variable.is-3-tablet {
|
||||
--columnGap: 0.75rem; } }
|
||||
@media screen and (min-width: 769px) and (max-width: 1087px) {
|
||||
.columns.is-variable.is-3-tablet-only {
|
||||
--columnGap: 0.75rem; } }
|
||||
@media screen and (max-width: 1087px) {
|
||||
.columns.is-variable.is-3-touch {
|
||||
--columnGap: 0.75rem; } }
|
||||
@media screen and (min-width: 1088px) {
|
||||
.columns.is-variable.is-3-desktop {
|
||||
--columnGap: 0.75rem; } }
|
||||
@media screen and (min-width: 1088px) and (max-width: 1279px) {
|
||||
.columns.is-variable.is-3-desktop-only {
|
||||
--columnGap: 0.75rem; } }
|
||||
@media screen and (min-width: 1280px) {
|
||||
.columns.is-variable.is-3-widescreen {
|
||||
--columnGap: 0.75rem; } }
|
||||
@media screen and (min-width: 1280px) and (max-width: 1471px) {
|
||||
.columns.is-variable.is-3-widescreen-only {
|
||||
--columnGap: 0.75rem; } }
|
||||
@media screen and (min-width: 1472px) {
|
||||
.columns.is-variable.is-3-fullhd {
|
||||
--columnGap: 0.75rem; } }
|
||||
.columns.is-variable.is-4 {
|
||||
--columnGap: 1rem; }
|
||||
@media screen and (max-width: 768px) {
|
||||
.columns.is-variable.is-4-mobile {
|
||||
--columnGap: 1rem; } }
|
||||
@media screen and (min-width: 769px), print {
|
||||
.columns.is-variable.is-4-tablet {
|
||||
--columnGap: 1rem; } }
|
||||
@media screen and (min-width: 769px) and (max-width: 1087px) {
|
||||
.columns.is-variable.is-4-tablet-only {
|
||||
--columnGap: 1rem; } }
|
||||
@media screen and (max-width: 1087px) {
|
||||
.columns.is-variable.is-4-touch {
|
||||
--columnGap: 1rem; } }
|
||||
@media screen and (min-width: 1088px) {
|
||||
.columns.is-variable.is-4-desktop {
|
||||
--columnGap: 1rem; } }
|
||||
@media screen and (min-width: 1088px) and (max-width: 1279px) {
|
||||
.columns.is-variable.is-4-desktop-only {
|
||||
--columnGap: 1rem; } }
|
||||
@media screen and (min-width: 1280px) {
|
||||
.columns.is-variable.is-4-widescreen {
|
||||
--columnGap: 1rem; } }
|
||||
@media screen and (min-width: 1280px) and (max-width: 1471px) {
|
||||
.columns.is-variable.is-4-widescreen-only {
|
||||
--columnGap: 1rem; } }
|
||||
@media screen and (min-width: 1472px) {
|
||||
.columns.is-variable.is-4-fullhd {
|
||||
--columnGap: 1rem; } }
|
||||
.columns.is-variable.is-5 {
|
||||
--columnGap: 1.25rem; }
|
||||
@media screen and (max-width: 768px) {
|
||||
.columns.is-variable.is-5-mobile {
|
||||
--columnGap: 1.25rem; } }
|
||||
@media screen and (min-width: 769px), print {
|
||||
.columns.is-variable.is-5-tablet {
|
||||
--columnGap: 1.25rem; } }
|
||||
@media screen and (min-width: 769px) and (max-width: 1087px) {
|
||||
.columns.is-variable.is-5-tablet-only {
|
||||
--columnGap: 1.25rem; } }
|
||||
@media screen and (max-width: 1087px) {
|
||||
.columns.is-variable.is-5-touch {
|
||||
--columnGap: 1.25rem; } }
|
||||
@media screen and (min-width: 1088px) {
|
||||
.columns.is-variable.is-5-desktop {
|
||||
--columnGap: 1.25rem; } }
|
||||
@media screen and (min-width: 1088px) and (max-width: 1279px) {
|
||||
.columns.is-variable.is-5-desktop-only {
|
||||
--columnGap: 1.25rem; } }
|
||||
@media screen and (min-width: 1280px) {
|
||||
.columns.is-variable.is-5-widescreen {
|
||||
--columnGap: 1.25rem; } }
|
||||
@media screen and (min-width: 1280px) and (max-width: 1471px) {
|
||||
.columns.is-variable.is-5-widescreen-only {
|
||||
--columnGap: 1.25rem; } }
|
||||
@media screen and (min-width: 1472px) {
|
||||
.columns.is-variable.is-5-fullhd {
|
||||
--columnGap: 1.25rem; } }
|
||||
.columns.is-variable.is-6 {
|
||||
--columnGap: 1.5rem; }
|
||||
@media screen and (max-width: 768px) {
|
||||
.columns.is-variable.is-6-mobile {
|
||||
--columnGap: 1.5rem; } }
|
||||
@media screen and (min-width: 769px), print {
|
||||
.columns.is-variable.is-6-tablet {
|
||||
--columnGap: 1.5rem; } }
|
||||
@media screen and (min-width: 769px) and (max-width: 1087px) {
|
||||
.columns.is-variable.is-6-tablet-only {
|
||||
--columnGap: 1.5rem; } }
|
||||
@media screen and (max-width: 1087px) {
|
||||
.columns.is-variable.is-6-touch {
|
||||
--columnGap: 1.5rem; } }
|
||||
@media screen and (min-width: 1088px) {
|
||||
.columns.is-variable.is-6-desktop {
|
||||
--columnGap: 1.5rem; } }
|
||||
@media screen and (min-width: 1088px) and (max-width: 1279px) {
|
||||
.columns.is-variable.is-6-desktop-only {
|
||||
--columnGap: 1.5rem; } }
|
||||
@media screen and (min-width: 1280px) {
|
||||
.columns.is-variable.is-6-widescreen {
|
||||
--columnGap: 1.5rem; } }
|
||||
@media screen and (min-width: 1280px) and (max-width: 1471px) {
|
||||
.columns.is-variable.is-6-widescreen-only {
|
||||
--columnGap: 1.5rem; } }
|
||||
@media screen and (min-width: 1472px) {
|
||||
.columns.is-variable.is-6-fullhd {
|
||||
--columnGap: 1.5rem; } }
|
||||
.columns.is-variable.is-7 {
|
||||
--columnGap: 1.75rem; }
|
||||
@media screen and (max-width: 768px) {
|
||||
.columns.is-variable.is-7-mobile {
|
||||
--columnGap: 1.75rem; } }
|
||||
@media screen and (min-width: 769px), print {
|
||||
.columns.is-variable.is-7-tablet {
|
||||
--columnGap: 1.75rem; } }
|
||||
@media screen and (min-width: 769px) and (max-width: 1087px) {
|
||||
.columns.is-variable.is-7-tablet-only {
|
||||
--columnGap: 1.75rem; } }
|
||||
@media screen and (max-width: 1087px) {
|
||||
.columns.is-variable.is-7-touch {
|
||||
--columnGap: 1.75rem; } }
|
||||
@media screen and (min-width: 1088px) {
|
||||
.columns.is-variable.is-7-desktop {
|
||||
--columnGap: 1.75rem; } }
|
||||
@media screen and (min-width: 1088px) and (max-width: 1279px) {
|
||||
.columns.is-variable.is-7-desktop-only {
|
||||
--columnGap: 1.75rem; } }
|
||||
@media screen and (min-width: 1280px) {
|
||||
.columns.is-variable.is-7-widescreen {
|
||||
--columnGap: 1.75rem; } }
|
||||
@media screen and (min-width: 1280px) and (max-width: 1471px) {
|
||||
.columns.is-variable.is-7-widescreen-only {
|
||||
--columnGap: 1.75rem; } }
|
||||
@media screen and (min-width: 1472px) {
|
||||
.columns.is-variable.is-7-fullhd {
|
||||
--columnGap: 1.75rem; } }
|
||||
.columns.is-variable.is-8 {
|
||||
--columnGap: 2rem; }
|
||||
@media screen and (max-width: 768px) {
|
||||
.columns.is-variable.is-8-mobile {
|
||||
--columnGap: 2rem; } }
|
||||
@media screen and (min-width: 769px), print {
|
||||
.columns.is-variable.is-8-tablet {
|
||||
--columnGap: 2rem; } }
|
||||
@media screen and (min-width: 769px) and (max-width: 1087px) {
|
||||
.columns.is-variable.is-8-tablet-only {
|
||||
--columnGap: 2rem; } }
|
||||
@media screen and (max-width: 1087px) {
|
||||
.columns.is-variable.is-8-touch {
|
||||
--columnGap: 2rem; } }
|
||||
@media screen and (min-width: 1088px) {
|
||||
.columns.is-variable.is-8-desktop {
|
||||
--columnGap: 2rem; } }
|
||||
@media screen and (min-width: 1088px) and (max-width: 1279px) {
|
||||
.columns.is-variable.is-8-desktop-only {
|
||||
--columnGap: 2rem; } }
|
||||
@media screen and (min-width: 1280px) {
|
||||
.columns.is-variable.is-8-widescreen {
|
||||
--columnGap: 2rem; } }
|
||||
@media screen and (min-width: 1280px) and (max-width: 1471px) {
|
||||
.columns.is-variable.is-8-widescreen-only {
|
||||
--columnGap: 2rem; } }
|
||||
@media screen and (min-width: 1472px) {
|
||||
.columns.is-variable.is-8-fullhd {
|
||||
--columnGap: 2rem; } }
|
||||
|
||||
.tile {
|
||||
align-items: stretch;
|
||||
@@ -6356,16 +6681,18 @@ label.panel-block {
|
||||
.hero.is-large .hero-body {
|
||||
padding-bottom: 18rem;
|
||||
padding-top: 18rem; } }
|
||||
.hero.is-halfheight .hero-body, .hero.is-fullheight .hero-body {
|
||||
.hero.is-halfheight .hero-body, .hero.is-fullheight .hero-body, .hero.is-fullheight-with-navbar .hero-body {
|
||||
align-items: center;
|
||||
display: flex; }
|
||||
.hero.is-halfheight .hero-body > .container, .hero.is-fullheight .hero-body > .container {
|
||||
.hero.is-halfheight .hero-body > .container, .hero.is-fullheight .hero-body > .container, .hero.is-fullheight-with-navbar .hero-body > .container {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1; }
|
||||
.hero.is-halfheight {
|
||||
min-height: 50vh; }
|
||||
.hero.is-fullheight {
|
||||
min-height: 100vh; }
|
||||
.hero.is-fullheight-with-navbar {
|
||||
min-height: calc(100vh - 3.25rem); }
|
||||
|
||||
.hero-video {
|
||||
overflow: hidden; }
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
; This is the service that broadcast market data to the
|
||||
; world. The users need to expect websocket and REST api.
|
||||
|
||||
|
||||
(ns skirmish.bookkeeper.core
|
||||
(:require [org.httpkit.server :refer [run-server]]
|
||||
[compojure.handler :refer [api]]
|
||||
|
||||
@@ -4,15 +4,50 @@
|
||||
[datomic.api :as d]
|
||||
[slingshot.slingshot :refer [try+ throw+]]
|
||||
[skirmish.rest.util :refer [build-response not-found-resp get-body]]
|
||||
[taoensso.timbre :as timbre]))
|
||||
[taoensso.timbre :as timbre]
|
||||
[skirmish.routes.core :as routes]
|
||||
[clj-http.client :as http-client]))
|
||||
|
||||
|
||||
(defn create-user [conn]
|
||||
(defn generate-amount-for-new-user [seed]
|
||||
(let [random (new java.util.Random seed)
|
||||
random-fn #(. random nextInt 20000)]
|
||||
(* 100 (reduce + (take 100 (repeatedly random-fn))))))
|
||||
|
||||
|
||||
(defn create-account [account-id sec-id]
|
||||
#:skirmish.broker.account{:id account-id
|
||||
:sec-id sec-id
|
||||
:private-key (str account-id)})
|
||||
|
||||
|
||||
(defn create-user-account [seed default-sec-id]
|
||||
(let [url (routes/get-path-for :ledger/security {:sec-id (name default-sec-id)})
|
||||
account-id (java.util.UUID/randomUUID)
|
||||
amount (generate-amount-for-new-user seed)
|
||||
payload [amount (str account-id)]]
|
||||
(timbre/info "Creating new account " account-id " with ammount " amount " of " default-sec-id)
|
||||
(timbre/info url)
|
||||
(http-client/post url {:body (str payload)})
|
||||
(create-account account-id default-sec-id)))
|
||||
|
||||
(defn create-user-seed-from-email [email]
|
||||
(let [hasher (java.security.MessageDigest/getInstance "SHA-256")
|
||||
salt (. "this should be private and set in config" getBytes "utf-8")
|
||||
msg (. email getBytes "utf-8")
|
||||
_ (doto hasher (.update salt) (.update msg))
|
||||
hash (. hasher digest)]
|
||||
(int (mod (new BigInteger 1 hash) (biginteger Integer/MAX_VALUE)))))
|
||||
|
||||
|
||||
(defn create-user [conn default-sec-id]
|
||||
(fn [{body :body :as request}]
|
||||
(try+
|
||||
(let [email (-> body get-body :email)]
|
||||
(timbre/info "Creating new user" email)
|
||||
@(d/transact conn [[:skirmish.broker.user/create-user email]])
|
||||
(let [email (-> body get-body :email)
|
||||
_ (timbre/info "Creating new user" email)
|
||||
seed (create-user-seed-from-email email)
|
||||
account (create-user-account seed default-sec-id)]
|
||||
@(d/transact conn [[:skirmish.broker.user/create-user email [account]]])
|
||||
(build-response request {:status 201 :headers {"location" (str "/api/1/users/" email)}}))
|
||||
(catch [:error-type :4XX] e (build-response request e))
|
||||
(catch [:error-type :5XX] e (build-response request e)))))
|
||||
@@ -24,6 +59,7 @@
|
||||
ret (map #(assoc % :skirmish.broker.account/url (url %)) accounts)]
|
||||
(assoc user :skirmish.broker.user/accounts ret)))
|
||||
|
||||
|
||||
(defn get-user [conn]
|
||||
(fn [{{email :email} :route-params :as request}]
|
||||
(try+
|
||||
@@ -54,14 +90,15 @@
|
||||
(catch [:error-type :4XX] e (build-response request e))
|
||||
(catch [:error-type :5XX] e (build-response request e)))))
|
||||
|
||||
|
||||
(defn get-account [conn]
|
||||
(fn [email account]))
|
||||
|
||||
|
||||
(defn create-routes [conn]
|
||||
(defn create-routes [conn config]
|
||||
(routes
|
||||
(context "/api/1" []
|
||||
(POST "/users" [] (create-user conn))
|
||||
(POST "/users" [] (create-user conn (:default-sec-id config)))
|
||||
(GET "/users" [] (get-all-users conn))
|
||||
(GET "/users/:email" [email] (get-user conn))
|
||||
(GET "/users/:email/accounts/:account" [email account] (get-account conn)))
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
; The backend of the skirmish web user interface.
|
||||
|
||||
(ns skirmish.broker.core
|
||||
(:require [org.httpkit.server :refer [run-server]]
|
||||
[compojure.handler :refer [api]]
|
||||
@@ -11,7 +13,7 @@
|
||||
(defn -main [config securities-api exchange-apis]
|
||||
(let [webserver-config (:api-broker config)
|
||||
conn (create-connection (-> webserver-config :datomic :transactor-url))
|
||||
api-routes (create-routes conn)]
|
||||
api-routes (create-routes conn webserver-config)]
|
||||
(timbre/info "Running skirmish.broker.core server with config: " webserver-config)
|
||||
(run-server
|
||||
(-> api-routes api cors-middleware)
|
||||
|
||||
@@ -8,12 +8,11 @@
|
||||
:db/doc "Create a new user in the system."
|
||||
:db/fn (d/function {:lang "clojure"
|
||||
:requires ['[slingshot.slingshot]]
|
||||
:params '[db email]
|
||||
:params '[db email accounts]
|
||||
:code '(let [existing-user (d/pull db '[:db/id] [:skirmish.broker.user/email email])]
|
||||
;:skirmish.broker.user/accounts []])]
|
||||
(when (-> existing-user :db/id some?)
|
||||
(slingshot.slingshot/throw+ {:error-type :4XX :status 400 :body {:error "A user with this email already exists."}}))
|
||||
[#:skirmish.broker.user{:email email}])})}])
|
||||
[#:skirmish.broker.user{:email email :accounts accounts}])})}])
|
||||
|
||||
|
||||
(def accounts-schema [
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
(ns skirmish.cli.benchmark
|
||||
(:require [clojure.math.combinatorics :refer [cartesian-product]]
|
||||
[clojure.core.async :refer [go chan <! >! close! to-chan close!] :as async]
|
||||
[gniazdo.core :as ws]
|
||||
[clj-http.client :as client]
|
||||
[com.stuartsierra.frequencies :as freq]
|
||||
[clojure.edn :as edn]))
|
||||
|
||||
|
||||
(def ids (atom 0))
|
||||
|
||||
|
||||
(defn web-socket-to-channel [url output-ch]
|
||||
(let [on-receive #(go (>! output-ch %))
|
||||
headers {"accept" "application/edn"}]
|
||||
(ws/connect url :headers headers :on-receive on-receive)))
|
||||
|
||||
|
||||
(defn create-orders-channel [securities]
|
||||
(let [output (chan 1 (map edn/read-string))
|
||||
url #(format "ws://localhost:%s/api/1/securities/%s/orders" 8080 %)
|
||||
xf (comp (map :sec-id)
|
||||
(map name)
|
||||
(map url)
|
||||
(map #(web-socket-to-channel % output)))]
|
||||
(doall (sequence xf securities))
|
||||
(async/pub output :book.order/id)))
|
||||
|
||||
|
||||
|
||||
(defn create-requests-payload-for-security [i security id]
|
||||
(let [rd (new java.util.Random i)
|
||||
rand-bool (= (mod (. rd nextInt) 2) 0) ;; java random number generator REALLY sucks
|
||||
type (if rand-bool :sell :buy)
|
||||
price (+ (* 3 (. rd nextGaussian)) 30)
|
||||
int-price (int (* price 100))
|
||||
quantity (reduce + 1 (map #(. rd nextInt (inc %)) (range 100)))]
|
||||
|
||||
{:type type :quantity quantity :price int-price :security security :benchmark/id id}))
|
||||
|
||||
|
||||
(defn create-requests-for-security [[i security]]
|
||||
(let [req-id (swap! ids inc)
|
||||
payload (create-requests-payload-for-security i (str (:sec-id security)) req-id)
|
||||
sec-id-str (-> security :sec-id name)
|
||||
params {:throw-exceptions false :content-type :edn :headers {"accept" "application/edn"}}
|
||||
url (format "http://localhost:%s/api/1/securities/%s" 8080 sec-id-str)]
|
||||
(fn []
|
||||
(let [now (System/nanoTime)]
|
||||
(client/post url (assoc params :form-params (assoc payload :benchmark/request-time now)))))))
|
||||
|
||||
|
||||
(defn make-requests [n securities]
|
||||
(map create-requests-for-security (cartesian-product (range n) securities)))
|
||||
|
||||
|
||||
(defn no-more-then-n-simultaneously [f input-chan n]
|
||||
(let [buffer (chan)]
|
||||
(go (async/pipeline n buffer (map f) input-chan))
|
||||
buffer))
|
||||
|
||||
(defn decode-response [response]
|
||||
(let [now (System/nanoTime)
|
||||
ret {}
|
||||
ret (assoc ret :status (:status response))
|
||||
ret (assoc ret :order (edn/read-string (:body response)))
|
||||
ret (assoc ret :duration (/ (- now (get-in ret [:order :benchmark/request-time])) (Math/pow 10 9)))]
|
||||
ret))
|
||||
|
||||
|
||||
(defn benchmark [requests orders]
|
||||
(let [decoded-responses (chan 1 (map decode-response))
|
||||
responses (no-more-then-n-simultaneously #(%) (to-chan requests) 10)]
|
||||
(go (async/pipe responses decoded-responses))
|
||||
decoded-responses))
|
||||
|
||||
|
||||
(defn compute-result [results new-result]
|
||||
(let [status-code (:status new-result)
|
||||
duration (:duration new-result)
|
||||
old-status-code-count (get-in results [:status status-code] 0)
|
||||
ret results
|
||||
ret (assoc-in ret [:status status-code] (inc old-status-code-count))
|
||||
ret (assoc ret :durations (conj (:durations ret) duration))]
|
||||
ret))
|
||||
|
||||
(defn pp-durations [durations]
|
||||
(let [freq-map (frequencies durations)]
|
||||
(print (freq/stats freq-map))))
|
||||
|
||||
(defn display-results [results]
|
||||
(println results)
|
||||
(println "number of request: " (:n results))
|
||||
(println "status map: " (:status results))
|
||||
(pp-durations (:durations results)))
|
||||
|
||||
(defn -main [config exchange-apis]
|
||||
(let [benchmark-config (:benchmark config)
|
||||
requests (-> benchmark-config :number-of-requests-per-securities (make-requests (:securities config)))
|
||||
orders (create-orders-channel (:securities config))
|
||||
results (benchmark requests orders)]
|
||||
(display-results (async/<!! (async/reduce compute-result {} results)))))
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
(ns skirmish.cli.core)
|
||||
@@ -1,56 +0,0 @@
|
||||
(ns skirmish.cli.exchange
|
||||
(:require [skirmish.exchange.match :refer [sell?]]
|
||||
[skirmish.util :refer :all]
|
||||
[skirmish.exchange.exchange :refer [empty-book create-exchange-for-security]]
|
||||
[skirmish.order] ;; for spec side effect
|
||||
[slingshot.slingshot :refer [try+ throw+]]
|
||||
[clojure.edn :as edn]
|
||||
[clojure.core.async :refer [chan go >!! <!!]]
|
||||
[clojure.spec.alpha :as s]))
|
||||
|
||||
|
||||
(def incoming-order-channel (chan))
|
||||
|
||||
;(def trades-channel (chan))
|
||||
;
|
||||
;(def new-book-order-channel (chan))
|
||||
|
||||
(defn print-chan [prefix c]
|
||||
(loop [shit (<!! c)]
|
||||
(println prefix shit)
|
||||
(recur (<!! c))))
|
||||
|
||||
;(def master-book {:sell (create-sell-book)
|
||||
; :buy (create-buy-book)})
|
||||
|
||||
;;; repl things.
|
||||
(defn read-order []
|
||||
(try
|
||||
(print "$ ")
|
||||
(let [order (edn/read-string (read-line))
|
||||
valid? (s/valid? :book.order/order order)]
|
||||
(if valid? order nil))
|
||||
(catch Exception e nil)))
|
||||
|
||||
(def order-ids (atom -1))
|
||||
|
||||
|
||||
(defn add-id-to-order [order]
|
||||
(assoc order :book.order/id (swap! order-ids inc)))
|
||||
|
||||
|
||||
(defn -main [& args]
|
||||
(let [initial-book empty-book
|
||||
incoming-order-channel (chan)
|
||||
[trades-channel new-book-order-channel] (create-exchange-for-security initial-book incoming-order-channel)]
|
||||
(go (print-chan "trade > " trades-channel))
|
||||
(go (print-chan "order > " new-book-order-channel))
|
||||
(loop [order (read-order)]
|
||||
(if (some? order)
|
||||
(do
|
||||
(let [order-with-id (add-id-to-order order)]
|
||||
(>!! incoming-order-channel order-with-id)
|
||||
(println "\n\norder accepted with id: " (:book.order/id order-with-id))))
|
||||
|
||||
(println "invalid order"))
|
||||
(recur (read-order)))))
|
||||
@@ -1,7 +1,5 @@
|
||||
(ns skirmish.core
|
||||
(:require [skirmish.cli.core]
|
||||
[skirmish.cli.benchmark]
|
||||
[skirmish.exchange.core]
|
||||
(:require [skirmish.exchange.core]
|
||||
[skirmish.wui.server]
|
||||
[skirmish.bookkeeper.core]
|
||||
[skirmish.exchange-gateway.core]
|
||||
|
||||
@@ -106,6 +106,3 @@
|
||||
(<! orders-copy)
|
||||
(<! trades-copy)
|
||||
(recur (<! new-order-publisher) new-book)))))
|
||||
|
||||
|
||||
|
||||
@@ -153,6 +153,11 @@
|
||||
|
||||
|
||||
(defn emit-securities [conn]
|
||||
"Emit securities in the specified wallet. This is the only way to
|
||||
create securities.
|
||||
|
||||
payload
|
||||
[:amount :acount]"
|
||||
(fn [{{sec-id-str :sec-id} :route-params body :body :as request}]
|
||||
(try+
|
||||
(let [emit-request (-> body get-body (get-emit-securities-request (keyword sec-id-str)))]
|
||||
@@ -184,7 +189,6 @@
|
||||
|
||||
(defn create-routes-for-ledger [conn]
|
||||
(compojure/routes
|
||||
;; (routes/get-context :ledger)
|
||||
(routes/context :ledger []
|
||||
(routes/GET :ledger/home [] get-api-description)
|
||||
(routes/GET :ledger/accounts [] (get-available-accounts conn))
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
(head)
|
||||
[:body.body-container
|
||||
mount-target
|
||||
(include-js "/js/app.js")]))
|
||||
(include-js "/js/app.js")
|
||||
(include-js "https://sidecar.gitter.im/dist/sidecar.v1.js")]))
|
||||
|
||||
(defn loading-page []
|
||||
(html5
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
(ns skirmish.wui.components.home-page
|
||||
(:require [cljs-http.client :as http]
|
||||
[reagent.core :refer [atom]]
|
||||
[skirmish.wui.util :refer [email?]]
|
||||
[cljs.core.async :refer [<!] :refer-macros [go]]
|
||||
[secretary.core :as secretary]))
|
||||
|
||||
(defn email? [s]
|
||||
(let [email-pattern #"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$"]
|
||||
(->> s str (re-matches email-pattern) some?)))
|
||||
|
||||
|
||||
(defn create-user [email]
|
||||
(go
|
||||
(let [url "http://localhost:1605/api/1/users"
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
(ns skirmish.wui.components.otc-page
|
||||
(:require [reagent.core :refer [atom]]
|
||||
[skirmish.wui.util :refer [email?]]))
|
||||
|
||||
|
||||
(defn set-amount [state event]
|
||||
(let [inserted-value (-> event .-target .-value)
|
||||
new-value (first (re-find #"^\d+(\.\d*)?" inserted-value))]
|
||||
(swap! state #(assoc % :value new-value))))
|
||||
|
||||
(defn set-email [state event]
|
||||
(let [new-email (-> event .-target .-value)]
|
||||
(swap! state #(assoc % :email new-email))))
|
||||
|
||||
(defn set-sec-id [state event]
|
||||
(let [sec-id (-> event .-target .-value)]
|
||||
(swap! state #(assoc % :sec-id sec-id))))
|
||||
|
||||
|
||||
|
||||
(defn ready-to-transfer? [state]
|
||||
(and (-> state :value js/parseInt pos?)
|
||||
(-> state :email email?)))
|
||||
|
||||
(defn direct-send-confirmation []
|
||||
[:div.model
|
||||
[:div.modal-background]
|
||||
[:div.modal-content
|
||||
[:div.modal-card
|
||||
[:header.modal-card-head
|
||||
[:p.modal-card-title "Modal title"]
|
||||
[:button.delete]]]]
|
||||
[:a.button.modal-close.is-large {:aria-label "close"} "dslkds"]])
|
||||
|
||||
(defn direct-send []
|
||||
(let [state (atom {})]
|
||||
(fn []
|
||||
[:div
|
||||
[:div.content
|
||||
[:p "Send securities to another person."]
|
||||
[:pre (str @state)]]
|
||||
[:form.form
|
||||
[:div.field.has-addons.has-addons-centered
|
||||
[:div.control
|
||||
[:span.select
|
||||
[:select {:value (:sec-id @state)
|
||||
:on-change (partial set-sec-id state)}
|
||||
[:option {:value :foo :selected true} "damp"]
|
||||
[:option {:value :bar } "blah blah blah"]]]]
|
||||
[:div.control
|
||||
[:input.input {:type "text"
|
||||
:value (:value @state)
|
||||
:on-change (partial set-amount state)
|
||||
:placeholder "110.90"}]]
|
||||
[:div.control
|
||||
[:input.input {:type "text"
|
||||
:value (:email @state)
|
||||
:on-change (partial set-email state)
|
||||
:placeholder "boris.johnson@redneck.co.uk"}]]
|
||||
[:a.button.is-primary {:style {:marginLeft "1em"}
|
||||
:disabled (-> @state ready-to-transfer? not)} "transfer"]]]])))
|
||||
|
||||
(defn page [state]
|
||||
[:div
|
||||
;[direct-send-confirmation]
|
||||
[:div.content
|
||||
[:h5 "Do transaction without passing by the exchange."]]
|
||||
|
||||
[direct-send]])
|
||||
@@ -23,8 +23,9 @@
|
||||
accounts (map :body (<! (async/into [] ar)))]
|
||||
(reset! a-accounts accounts))))
|
||||
|
||||
(defn shit [urls accounts]
|
||||
(defn page-wrapper [accounts]
|
||||
[:div.content
|
||||
[:h5 "View all the assets you own."]
|
||||
[make-portfolio-summary-table accounts]])
|
||||
|
||||
|
||||
@@ -32,4 +33,4 @@
|
||||
(let [urls (map :skirmish.broker.account/url accounts)
|
||||
a-accounts (atom [])]
|
||||
(get-accounts-information urls a-accounts)
|
||||
#(shit urls @a-accounts)))
|
||||
#(page-wrapper @a-accounts)))
|
||||
@@ -49,7 +49,9 @@
|
||||
(defn create-currencies-table [securities]
|
||||
[:div
|
||||
[:div.content
|
||||
[:h3 "Currencies"]]
|
||||
[:h3 "Currencies"]
|
||||
[:p "A " [:em "currency"] " is currency that pays interest rate in
|
||||
its own nomination."]]
|
||||
[:table.table.is-hoverable.is-fullwidth
|
||||
table-head
|
||||
[:tbody
|
||||
@@ -58,7 +60,11 @@
|
||||
(defn create-equities-table [securities]
|
||||
[:div
|
||||
[:div.content
|
||||
[:h3 "Equities"]]
|
||||
[:h3 "Equities"]
|
||||
[:p "An " [:em "equity"] " is a type of security that pays dividends. In
|
||||
real life, an equity (or stock) is a share in a company ownership.
|
||||
When the company make profits, it shares the its profits between its stock
|
||||
holder in a pro rata fashion."]]
|
||||
[:table.table.is-hoverable.is-fullwidth
|
||||
table-head
|
||||
[:tbody
|
||||
@@ -76,4 +82,7 @@
|
||||
#(create-securities-table @securities)))
|
||||
|
||||
(defn page [state]
|
||||
(security-table))
|
||||
[:div
|
||||
[:div.content
|
||||
[:h5 "View the available securities for trading."]]
|
||||
[security-table]])
|
||||
@@ -6,12 +6,29 @@
|
||||
|
||||
(def done {:style {:text-decoration "line-through"}})
|
||||
|
||||
|
||||
(defn filter-out-done [n]
|
||||
(let [state (volatile! n)]
|
||||
(fn [item]
|
||||
(when (= (second item) done)
|
||||
(vswap! state dec))
|
||||
(or (not= (second item) done) (neg? @state)))))
|
||||
|
||||
|
||||
(defn todo-list [& items]
|
||||
(let [filter-fn (filter-out-done (- (count items) 20))
|
||||
item-to-show (filter filter-fn items)]
|
||||
[:ol
|
||||
(for [item item-to-show]
|
||||
item)]))
|
||||
|
||||
(defn page []
|
||||
[:div.content
|
||||
[:h5 "This still to do in this project. This is a jira for the poor (or for the geek)."]
|
||||
[:ol
|
||||
[:li "Broker"
|
||||
[:p "the brooker should use the exchange-gateway and ledge as apis. Nothing else."]
|
||||
[:ol
|
||||
[todo-list
|
||||
[:li done "implement basic login in the wui"]
|
||||
[:li done "implement basic web server"]
|
||||
[:li done "implement stupid login on server side."]
|
||||
@@ -22,14 +39,18 @@
|
||||
[:li done "make the " [:a {:href "/portfolio"} "portfolio"] " page representing the use accounts"]
|
||||
[:li done "make a somewhat pretty " [:a {:href "/securities/dapm"} "security"] " page"]
|
||||
[:li done "create two stocks"]
|
||||
[:li on-it "add stocks to didier@gmail.com"]
|
||||
[:li on-it "make something for urls on the backend"]
|
||||
[:li done "add stocks to didier@gmail.com"]
|
||||
[:li done "make something for urls on the backend"]
|
||||
[:li on-it "add a wui for OTC"]
|
||||
[:li "fix the stupid chat. This is an important feature for OTC users."]
|
||||
[:li "make something that allows to send securities from one user to another"]
|
||||
[:li "move 'routes' in 'rest'"]
|
||||
[:li "add a little bit of professionalism on top of the broker api"]
|
||||
[:li "make the api support transit and edn"]
|
||||
[:li "implements interest rate in ledger."]
|
||||
[:li "Make sure the ledger can have accounts"]
|
||||
[:li "when a new user signs in, he should receive some sort of money"]
|
||||
[:li "\u22EE"]
|
||||
[:li done "when a new user signs in, he should receive some sort of money"]
|
||||
[:li "implement send securities to friends"]
|
||||
[:li "implement buy/sell securities."]
|
||||
[:li "Use ledger as source of through in each sub server."]
|
||||
[:li "Update web ui for integrating securities with ledger."]
|
||||
|
||||
@@ -37,8 +37,9 @@
|
||||
[:div.navbar-start
|
||||
[:a.navbar-item.is-tab {:href "/" :class (top-bar-class state :home)} "Home"]
|
||||
[:a.navbar-item.is-tab {:href "/securities" :class (top-bar-class state :securities)} "Securities"]
|
||||
[:a.navbar-item.is-tab {:href "/create-order" :class (top-bar-class state :create-order)} "Create order"]
|
||||
[:a.navbar-item.is-tab {:href "/portfolio" :class (top-bar-class state :view-portfolio)} "View Portfolio"]
|
||||
[:a.navbar-item.is-tab {:href "/create-order" :class (top-bar-class state :create-order)} "Create Order"]
|
||||
[:a.navbar-item.is-tab {:href "/otc" :class (top-bar-class state :otc)} "Over The Counter"]
|
||||
[:a.navbar-item.is-tab {:href "/portfolio" :class (top-bar-class state :view-portfolio)} "Portfolio"]
|
||||
[:a.navbar-item.is-tab {:href "/todo" :class (top-bar-class state :todo)} "Todo"]]
|
||||
[:div.navbar-end
|
||||
[:a.navbar-item {} "Skirmish Shitty Exchange"]
|
||||
|
||||
@@ -53,6 +53,12 @@
|
||||
(session/put! :current-page #'pages/portfolio-page)
|
||||
(swap! state #(into % {:page :portfolio})))
|
||||
|
||||
(secretary/defroute "/otc" []
|
||||
(session/put! :current-page #'pages/otc-page)
|
||||
(swap! state #(into % {:page :otc})))
|
||||
|
||||
|
||||
|
||||
(secretary/defroute "/create-order" []
|
||||
(session/put! :current-page #'pages/create-order-page)
|
||||
(swap! state #(into % {:page :create-order})))
|
||||
@@ -77,6 +83,7 @@
|
||||
[(session/get :current-page) @state]]]]])
|
||||
|
||||
|
||||
|
||||
(defn mount-root []
|
||||
(reagent/render [current-page] (.getElementById js/document "app")))
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
[skirmish.wui.components.portfolio-page]
|
||||
[skirmish.wui.components.create-order-page]
|
||||
[skirmish.wui.components.securities-page]
|
||||
[skirmish.wui.components.security-page]))
|
||||
[skirmish.wui.components.security-page]
|
||||
[skirmish.wui.components.otc-page]))
|
||||
|
||||
(def home-page skirmish.wui.components.home-page/page)
|
||||
|
||||
@@ -17,3 +18,5 @@
|
||||
(def portfolio-page skirmish.wui.components.portfolio-page/page)
|
||||
|
||||
(def security-page skirmish.wui.components.security-page/page)
|
||||
|
||||
(def otc-page skirmish.wui.components.otc-page/page)
|
||||
@@ -27,3 +27,8 @@
|
||||
(pp-int n)
|
||||
(let [i (int (Math/pow 10 dec))]
|
||||
(str (pp-int (quot n i)) "," (right-pad (str (mod n i)) "0" dec))))))
|
||||
|
||||
(defn email? [s]
|
||||
(let [email-pattern #"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$"]
|
||||
(->> s str (re-matches email-pattern) some?)))
|
||||
|
||||
@@ -25,6 +25,10 @@ $table-background-color: $grey-dark;
|
||||
$table-cell-heading-color: $grey-darker;
|
||||
$table-row-hover-background-color: $black-ter;
|
||||
|
||||
// box
|
||||
$box-background-color: $grey;
|
||||
$box-color: $white;
|
||||
$box-shadow: 0 2px 3px $grey-light, 0 0 0 1px $grey-light;// $red;
|
||||
|
||||
@import "../../node_modules/bulma/sass/base/_all";
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
(ns skirmish.broker.broker-test
|
||||
(:require [midje.sweet :refer :all]
|
||||
[orchestra.spec.test :as st]
|
||||
[skirmish.broker.broker :refer [add-account-urls]]))
|
||||
[skirmish.broker.broker :refer [add-account-urls
|
||||
create-user-seed-from-email
|
||||
generate-amount-for-new-user]]))
|
||||
|
||||
(st/instrument)
|
||||
|
||||
@@ -13,3 +15,19 @@
|
||||
:skirmish.broker.account/url "http://localhost:1604/api/1/ledger/accounts/foo"}]}]
|
||||
(add-account-urls user) => ret)))
|
||||
|
||||
(facts "about the new users"
|
||||
(fact "we should add some random amount of damp in their account"
|
||||
(let [random-values (map generate-amount-for-new-user (range 10000))
|
||||
sum (reduce + random-values)
|
||||
mean (/ sum (count random-values))
|
||||
min-data (apply min random-values)
|
||||
max-data (apply max random-values)]
|
||||
(int mean) => 100027427
|
||||
min-data => 78422400
|
||||
max-data => 120128000))
|
||||
|
||||
(fact "we should generate the random seed by the email"
|
||||
(create-user-seed-from-email "foo@gmail.com") => 399122929
|
||||
(create-user-seed-from-email "boo@gmail.com") => 1433675645))
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
(:require [midje.sweet :refer :all]
|
||||
[orchestra.spec.test :as st]
|
||||
[datomic.api :as d]
|
||||
[skirmish.broker.db :refer [create-connection]]))
|
||||
[skirmish.broker.db :refer [create-connection]]
|
||||
[skirmish.broker.broker :refer [create-account]]))
|
||||
|
||||
(st/instrument)
|
||||
|
||||
@@ -15,7 +16,10 @@
|
||||
(fact "should be able to create new users"
|
||||
(let [conn (create-conn)
|
||||
email "foo@gmail.com"
|
||||
_ @(d/transact conn [[:skirmish.broker.user/create-user email]])
|
||||
account-id #uuid"dc0d1123-e659-4e8a-b38b-ece520f4c4d0"
|
||||
account (create-account account-id :dapm)
|
||||
_ @(d/transact conn [[:skirmish.broker.user/create-user email account]])
|
||||
db (d/db conn)
|
||||
user (d/pull db '[*] [:skirmish.broker.user/email email])]
|
||||
(:skirmish.broker.user/email user) => email)));
|
||||
user (d/pull db '[* {:skirmish.broker.user/accounts [*]}] [:skirmish.broker.user/email email])]
|
||||
(:skirmish.broker.user/email user) => email
|
||||
(-> user :skirmish.broker.user/accounts first :skirmish.broker.account/id) => account-id)))
|
||||
@@ -1,30 +0,0 @@
|
||||
(ns skirmish.cli.benchmark-test
|
||||
(:require [skirmish.cli.benchmark :refer :all]
|
||||
[midje.sweet :refer :all]
|
||||
[clojure.core.async :as async]))
|
||||
|
||||
(facts "creating simple orders"
|
||||
(fact "buy order"
|
||||
(let [buy-order (create-requests-payload-for-security 666 (str :equity/ibm) 1)
|
||||
expt {:benchmark/id 1 :price 2922 :quantity 2476 :security ":equity/ibm" :type :buy}]
|
||||
buy-order => expt));;
|
||||
(fact "sell order"
|
||||
(let [buy-order (create-requests-payload-for-security 667 (str :equity/ibm) 2)
|
||||
expt {:benchmark/id 2 :price 2740 :quantity 2516 :security ":equity/ibm" :type :sell}]
|
||||
buy-order => expt)))
|
||||
|
||||
(facts "the utility functions"
|
||||
(fact "no-more-then-n-simultaneously"
|
||||
(let [simultatious (atom [])
|
||||
input-chan (async/to-chan (range 10))
|
||||
sleep-time #(+ (mod (+ (* 5142 %) 8765) 200) 50)
|
||||
f (fn [i]
|
||||
(swap! simultatious #(conj % {:id i :status :start :value 1}))
|
||||
(Thread/sleep (sleep-time i))
|
||||
(swap! simultatious #(conj % {:id i :status :end :value -1}))
|
||||
i)
|
||||
res (async/<!! (async/into [] (no-more-then-n-simultaneously f input-chan 3)))
|
||||
simultatious-over-time (reductions #(+ %1 (:value %2)) 0 @simultatious)]
|
||||
(apply max simultatious-over-time) => 3
|
||||
(first simultatious-over-time) => 0
|
||||
(last simultatious-over-time) => 0)))
|
||||
Reference in new issue
Block a user