From a35b5a09b01e09dd77982c060b8394f5c9bcdaff Mon Sep 17 00:00:00 2001 From: Pete Vilter Date: Sun, 24 Nov 2024 19:02:33 -0500 Subject: [PATCH] Market tweaks (#499) * split into own folder * round the amounts * sort the order book * add test * don't let one sell go to multiple buys --- apps/actors/systems/kvSync/ddTests.ts | 2 +- .../kvSync/examples/commodityMarket.tsx | 328 ----- .../kvSync/examples/commodityMarket/choose.ts | 32 + .../commodityMarket.dd.txt | 1107 +++++++---------- .../kvSync/examples/commodityMarket/index.ts | 17 + .../examples/commodityMarket/mutations.ts | 86 ++ .../kvSync/examples/commodityMarket/types.ts | 48 + .../kvSync/examples/commodityMarket/ui.tsx | 170 +++ 8 files changed, 833 insertions(+), 957 deletions(-) delete mode 100644 apps/actors/systems/kvSync/examples/commodityMarket.tsx create mode 100644 apps/actors/systems/kvSync/examples/commodityMarket/choose.ts rename apps/actors/systems/kvSync/examples/{ => commodityMarket}/commodityMarket.dd.txt (67%) create mode 100644 apps/actors/systems/kvSync/examples/commodityMarket/index.ts create mode 100644 apps/actors/systems/kvSync/examples/commodityMarket/mutations.ts create mode 100644 apps/actors/systems/kvSync/examples/commodityMarket/types.ts create mode 100644 apps/actors/systems/kvSync/examples/commodityMarket/ui.tsx diff --git a/apps/actors/systems/kvSync/ddTests.ts b/apps/actors/systems/kvSync/ddTests.ts index ab9a1fcf..922ed3ec 100644 --- a/apps/actors/systems/kvSync/ddTests.ts +++ b/apps/actors/systems/kvSync/ddTests.ts @@ -51,7 +51,7 @@ export function kvSyncTests(writeResults: boolean): Suite { name: "commodityMarket", test() { runDDTestAtPath( - "apps/actors/systems/kvSync/examples/commodityMarket.dd.txt", + "apps/actors/systems/kvSync/examples/commodityMarket/commodityMarket.dd.txt", (inputs) => kvSyncTest(commodityMarket, inputs), writeResults ); diff --git a/apps/actors/systems/kvSync/examples/commodityMarket.tsx b/apps/actors/systems/kvSync/examples/commodityMarket.tsx deleted file mode 100644 index 55f018fc..00000000 --- a/apps/actors/systems/kvSync/examples/commodityMarket.tsx +++ /dev/null @@ -1,328 +0,0 @@ -import React, { useState } from "react"; -import { KVApp } from "./types"; -import { - MutationCtx, - MutationInvocation, - TSMutationDefns, - UserInput, - WriteOp, -} from "../types"; -import { Client, makeClient, useLiveQuery } from "../hooks"; -import { ChooseFn, UIProps } from "../../../types"; -import { ClientState, QueryStatus, TransactionState } from "../client"; -import { LoginWrapper } from "../uiCommon/loginWrapper"; -import { Inspector } from "../uiCommon/inspector"; -import { Table } from "../../../../../uiCommon/generic/table"; -import { LoggedInHeader } from "../uiCommon/loggedInHeader"; -import { Json } from "../../../../../util/json"; -import { randomFromList, randStep2 } from "../../../../../util/util"; - -function MarketUI(props: UIProps) { - const client = makeClient(props); - - return ( - } - /> - ); -} - -function MarketInner(props: { client: Client; user: string }) { - const [orders, orderQueryStatus] = useOrders(props.client); - const [trades, tradeQueryStatus] = useTrades(props.client); - - return ( - <> - -

Market

-
- -

Orders

- - {orderQueryStatus === "Loading" ? ( - Loading... - ) : ( - - data={orders} - getKey={(order) => order.id.toString()} - columns={[ - { name: "id", render: (order) => order.id }, - { name: "price", render: (order) => `$${order.price}` }, - { name: "amount", render: (order) => order.amount }, - { name: "side", render: (order) => order.side }, - { name: "user", render: (order) => order.user }, - { name: "status", render: (order) => order.status }, - ]} - /> - )} - -

Create Order

- - - -

Trades

- - {tradeQueryStatus === "Loading" ? ( - Loading... - ) : ( - - data={trades} - getKey={(trade) => trade.id.toString()} - columns={[ - { name: "id", render: (trade) => trade.id }, - { name: "price", render: (trade) => `$${trade.price}` }, - { name: "amount", render: (trade) => trade.amount }, - { name: "buy order", render: (trade) => trade.buyOrder }, - { name: "sell order", render: (trade) => trade.sellOrder }, - ]} - /> - )} - - - - ); -} - -function OrderForm(props: { client: Client }) { - const [price, setPrice] = useState(10); - const [amount, setAmount] = useState(100); - const [side, setSide] = useState("buy"); - - return ( -
{ - evt.preventDefault(); - - props.client.runMutation("Order", [price, amount, side]); - }} - > - Amount:{" "} - setAmount(parseInt(evt.target.value))} - />{" "} - Price:{" "} - setPrice(parseInt(evt.target.value))} - /> -
- - -
- -
- ); -} - -type OfferStatus = "open" | "sold"; - -type OrderSide = "sell" | "buy"; - -type Order = { - id: number; - price: number; - amount: number; - side: OrderSide; - user: string; - status: OfferStatus; -}; - -type Trade = { - id: number; - price: number; - amount: number; - buyOrder: number; - sellOrder: number; -}; - -type OrderWithState = Order & { state: TransactionState }; - -function useOrders(client: Client): [OrderWithState[], QueryStatus] { - const [rawOrders, queryStatus] = useLiveQuery(client, "list-orders", { - prefix: "/orders/", - }); - - const orders = Object.entries(rawOrders).map( - ([id, rawOrder]): OrderWithState => { - const order = rawOrder.value as any; - const mapped = readOrder(order); - return { - ...mapped, - state: client.state.transactions[rawOrder.transactionID]?.state, - }; - } - ); - - return [orders, queryStatus]; -} - -function readOrder(rawOrder: Json): Order { - const order = rawOrder as any; - return { - id: order.id as number, - price: order.price as number, - amount: order.amount as number, - status: order.status as OfferStatus, - side: order.side as OrderSide, - user: order.user as string, - }; -} - -function useTrades(client: Client): [Trade[], QueryStatus] { - const [rawTrades, queryStatus] = useLiveQuery(client, "list-trades", { - prefix: "/trades/", - }); - - const trades = Object.entries(rawTrades).map(([id, rawTrade]): Trade => { - const trade = rawTrade.value as any; - return readTrade(trade); - }); - - return [trades, queryStatus]; -} - -function readTrade(rawTrade: Json): Trade { - const trade = rawTrade as any; - return { - id: trade.id as number, - price: trade.price as number, - amount: trade.amount as number, - buyOrder: trade.buyOrder as number, - sellOrder: trade.sellOrder as number, - }; -} - -const mutations: TSMutationDefns = { - Order: (ctx, [price, amount, side]) => { - const id = ctx.rand(); - ctx.write(`/orders/${id}`, { - id, - price, - amount, - side, - status: "open", - user: ctx.curUser, - }); - }, -}; - -function matchOrders(ctx: MutationCtx, evt: WriteOp) { - // Prevent us from going on forever - if (evt.desc.type !== "Insert") { - return; - } - - const orders = ctx.scan("/orders/").map(readOrder); - - const buys = orders - .filter((order) => order.side === "buy" && order.status === "open") - .sort((a, b) => b.price - a.price); - const sells = orders - .filter((order) => order.side === "sell" && order.status === "open") - .sort((a, b) => a.price - b.price); - - for (const buy of buys) { - // TODO: keep buying while there's more to buy - for (const sell of sells) { - if (buy.price >= sell.price) { - const amount = Math.min(buy.amount, sell.amount); - const price = (buy.price + sell.price) / 2; - - // Execute the trade - const newBuyAmount = buy.amount - amount; - const newBuy: Order = { - ...buy, - amount: newBuyAmount, - status: newBuyAmount === 0 ? "sold" : "open", - }; - ctx.write(`/orders/${buy.id}`, newBuy); - - const newSellAmount = sell.amount - amount; - const newSell: Order = { - ...sell, - amount: newSellAmount, - status: newSellAmount === 0 ? "sold" : "open", - }; - ctx.write(`/orders/${sell.id}`, newSell); - - // console.log("matchOrders", { newBuy, newSell }); - - const tradeID = ctx.rand(); - ctx.write(`/trades/${tradeID}`, { - id: tradeID, - buyOrder: buy.id, - sellOrder: sell.id, - amount, - price, - }); - - if (newBuyAmount === 0) { - break; - } - } - } - } -} - -function choose( - clients: { - [id: string]: ClientState; - }, - randomSeed: number -): [{ clientID: string; invocation: MutationInvocation } | null, number] { - const [clientID, randomSeed1] = randomFromList( - randomSeed, - Object.keys(clients) - ); - - const [amount1, randomSeed2] = randStep2(randomSeed1); - const [price, randomSeed3] = randStep2(randomSeed2); - const side = randomFromList(randomSeed3, ["buy", "sell"])[0] as OrderSide; - - return [ - { - clientID, - invocation: { - type: "Invocation", - name: "Order", - args: [price * 100, amount1 * 100, side], - }, - }, - randomSeed3, - ]; -} - -export const commodityMarket: KVApp = { - name: "Commodity Market", - mutations, - ui: MarketUI, - triggers: [ - { - prefix: "/orders/", - fn: matchOrders, - }, - ], - choose, -}; diff --git a/apps/actors/systems/kvSync/examples/commodityMarket/choose.ts b/apps/actors/systems/kvSync/examples/commodityMarket/choose.ts new file mode 100644 index 00000000..c19d56c3 --- /dev/null +++ b/apps/actors/systems/kvSync/examples/commodityMarket/choose.ts @@ -0,0 +1,32 @@ +import { randomFromList, randStep2 } from "../../../../../../util/util"; +import { ClientState } from "../../client"; +import { MutationInvocation } from "../../types"; +import { OrderSide } from "./types"; + +export function choose( + clients: { + [id: string]: ClientState; + }, + randomSeed: number +): [{ clientID: string; invocation: MutationInvocation } | null, number] { + const [clientID, randomSeed1] = randomFromList( + randomSeed, + Object.keys(clients) + ); + + const [amount1, randomSeed2] = randStep2(randomSeed1); + const [price, randomSeed3] = randStep2(randomSeed2); + const side = randomFromList(randomSeed3, ["buy", "sell"])[0] as OrderSide; + + return [ + { + clientID, + invocation: { + type: "Invocation", + name: "Order", + args: [Math.round(price * 100), Math.round(amount1 * 100), side], + }, + }, + randomSeed3, + ]; +} diff --git a/apps/actors/systems/kvSync/examples/commodityMarket.dd.txt b/apps/actors/systems/kvSync/examples/commodityMarket/commodityMarket.dd.txt similarity index 67% rename from apps/actors/systems/kvSync/examples/commodityMarket.dd.txt rename to apps/actors/systems/kvSync/examples/commodityMarket/commodityMarket.dd.txt index 30bcb41d..0f421730 100644 --- a/apps/actors/systems/kvSync/examples/commodityMarket.dd.txt +++ b/apps/actors/systems/kvSync/examples/commodityMarket/commodityMarket.dd.txt @@ -108,7 +108,10 @@ addClient{id: "0"}. signUp{clientID: "0", username: "alice", password: ""}. addClient{id: "1"}. signUp{clientID: "1", username: "bob", password: ""}. -explore{seed: 123548762349586, steps: 100}. +runMutation{from: "0", name: "Order", args: [10, 100, "buy"]}. +runMutation{from: "0", name: "Order", args: [10, 100, "buy"]}. +runMutation{from: "0", name: "Order", args: [10, 100, "buy"]}. +runMutation{from: "0", name: "Order", args: [10, 100, "sell"]}. .jsonState ---- application/json @@ -119,21 +122,21 @@ application/json "transactionID": "0.00037566525896607457", "value": { "id": 0.31381383055244927, - "price": 37.316768604625736, - "amount": 20.142637957020327, - "side": "sell", + "price": 10, + "amount": 100, + "side": "buy", "status": "open", "user": "alice" } }, { - "transactionID": "0.5728273955852048", + "transactionID": "0.2581415779498793", "value": { "id": 0.31381383055244927, - "price": 37.316768604625736, + "price": 10, "amount": 0, "status": "sold", - "side": "sell", + "side": "buy", "user": "alice" } } @@ -143,21 +146,166 @@ application/json "transactionID": "0.26905546502122235", "value": { "id": 0.015206331866985496, - "price": 16.929892745735025, - "amount": 85.09055332754791, + "price": 10, + "amount": 100, + "side": "buy", + "status": "open", + "user": "alice" + } + } + ], + "/orders/0.5100409435201818": [ + { + "transactionID": "0.5728273955852048", + "value": { + "id": 0.5100409435201818, + "price": 10, + "amount": 100, "side": "buy", "status": "open", "user": "alice" } + } + ], + "/orders/0.5855064094862961": [ + { + "transactionID": "0.2581415779498793", + "value": { + "id": 0.5855064094862961, + "price": 10, + "amount": 100, + "side": "sell", + "status": "open", + "user": "alice" + } }, { "transactionID": "0.2581415779498793", + "value": { + "id": 0.5855064094862961, + "price": 10, + "amount": 0, + "status": "sold", + "side": "sell", + "user": "alice" + } + } + ], + "/trades/0.6062274799740198": [ + { + "transactionID": "0.2581415779498793", + "value": { + "id": 0.6062274799740198, + "buyOrder": 0.31381383055244927, + "sellOrder": 0.5855064094862961, + "amount": 100, + "price": 10 + } + } + ] + }, + "user0": {}, + "client0": { + "/orders/0.31381383055244927": [ + { + "transactionID": "0.00037566525896607457", + "value": { + "id": 0.31381383055244927, + "price": 10, + "amount": 100, + "side": "buy", + "status": "open", + "user": "alice" + } + } + ], + "/orders/0.015206331866985496": [ + { + "transactionID": "0.26905546502122235", "value": { "id": 0.015206331866985496, - "price": 16.929892745735025, - "amount": 74.38574868662819, + "price": 10, + "amount": 100, + "side": "buy", + "status": "open", + "user": "alice" + } + } + ], + "/orders/0.5100409435201818": [ + { + "transactionID": "0.5728273955852048", + "value": { + "id": 0.5100409435201818, + "price": 10, + "amount": 100, + "side": "buy", + "status": "open", + "user": "alice" + } + } + ], + "/orders/0.5855064094862961": [ + { + "transactionID": "0.2581415779498793", + "value": { + "id": 0.5855064094862961, + "price": 10, + "amount": 100, + "side": "sell", + "status": "open", + "user": "alice" + } + } + ] + }, + "user1": {}, + "client1": {} +} + +addClient{id: "0"}. +signUp{clientID: "0", username: "alice", password: ""}. +addClient{id: "1"}. +signUp{clientID: "1", username: "bob", password: ""}. +explore{seed: 123548762349586, steps: 100}. +.jsonState +---- +application/json +{ + "server": { + "/orders/0.31381383055244927": [ + { + "transactionID": "0.00037566525896607457", + "value": { + "id": 0.31381383055244927, + "price": 37, + "amount": 20, + "side": "sell", "status": "open", + "user": "alice" + } + }, + { + "transactionID": "0.5728273955852048", + "value": { + "id": 0.31381383055244927, + "price": 37, + "amount": 0, + "status": "sold", + "side": "sell", + "user": "alice" + } + } + ], + "/orders/0.015206331866985496": [ + { + "transactionID": "0.26905546502122235", + "value": { + "id": 0.015206331866985496, + "price": 17, + "amount": 85, "side": "buy", + "status": "open", "user": "alice" } }, @@ -165,8 +313,8 @@ application/json "transactionID": "0.619620117004607", "value": { "id": 0.015206331866985496, - "price": 16.929892745735025, - "amount": 67.19771946519401, + "price": 17, + "amount": 78, "status": "open", "side": "buy", "user": "alice" @@ -176,8 +324,8 @@ application/json "transactionID": "0.787366886890835", "value": { "id": 0.015206331866985496, - "price": 16.929892745735025, - "amount": 32.872100763835114, + "price": 17, + "amount": 44, "status": "open", "side": "buy", "user": "alice" @@ -187,7 +335,7 @@ application/json "transactionID": "0.8598554999193694", "value": { "id": 0.015206331866985496, - "price": 16.929892745735025, + "price": 17, "amount": 0, "status": "sold", "side": "buy", @@ -200,8 +348,8 @@ application/json "transactionID": "0.5728273955852048", "value": { "id": 0.5100409435201818, - "price": 60.20900012013409, - "amount": 88.19302724506056, + "price": 60, + "amount": 88, "side": "buy", "status": "open", "user": "alice" @@ -211,8 +359,8 @@ application/json "transactionID": "0.5728273955852048", "value": { "id": 0.5100409435201818, - "price": 60.20900012013409, - "amount": 68.05038928804024, + "price": 60, + "amount": 68, "status": "open", "side": "buy", "user": "alice" @@ -222,8 +370,8 @@ application/json "transactionID": "0.2581415779498793", "value": { "id": 0.5100409435201818, - "price": 60.20900012013409, - "amount": 57.34558464712052, + "price": 60, + "amount": 57, "status": "open", "side": "buy", "user": "alice" @@ -233,7 +381,7 @@ application/json "transactionID": "0.0003834916282291446", "value": { "id": 0.5100409435201818, - "price": 60.20900012013409, + "price": 60, "amount": 0, "status": "sold", "side": "buy", @@ -248,8 +396,8 @@ application/json "id": 0.2581415779498793, "buyOrder": 0.5100409435201818, "sellOrder": 0.31381383055244927, - "amount": 20.142637957020327, - "price": 48.762884362379914 + "amount": 20, + "price": 48.5 } } ], @@ -258,8 +406,8 @@ application/json "transactionID": "0.2581415779498793", "value": { "id": 0.5855064094862961, - "price": 15.652298755619952, - "amount": 10.70480464091972, + "price": 16, + "amount": 11, "side": "sell", "status": "open", "user": "alice" @@ -269,18 +417,7 @@ application/json "transactionID": "0.2581415779498793", "value": { "id": 0.5855064094862961, - "price": 15.652298755619952, - "amount": 0, - "status": "sold", - "side": "sell", - "user": "alice" - } - }, - { - "transactionID": "0.2581415779498793", - "value": { - "id": 0.5855064094862961, - "price": 15.652298755619952, + "price": 16, "amount": 0, "status": "sold", "side": "sell", @@ -295,20 +432,8 @@ application/json "id": 0.6062274799740198, "buyOrder": 0.5100409435201818, "sellOrder": 0.5855064094862961, - "amount": 10.70480464091972, - "price": 37.93064943787702 - } - } - ], - "/trades/0.8652590050969823": [ - { - "transactionID": "0.2581415779498793", - "value": { - "id": 0.8652590050969823, - "buyOrder": 0.015206331866985496, - "sellOrder": 0.5855064094862961, - "amount": 10.70480464091972, - "price": 16.29109575067749 + "amount": 11, + "price": 38 } } ], @@ -317,8 +442,8 @@ application/json "transactionID": "0.0003834916282291446", "value": { "id": 0.4453516187568676, - "price": 45.81208093633138, - "amount": 91.73236223099042, + "price": 46, + "amount": 92, "side": "sell", "status": "open", "user": "bob" @@ -328,8 +453,8 @@ application/json "transactionID": "0.0003834916282291446", "value": { "id": 0.4453516187568676, - "price": 45.81208093633138, - "amount": 34.3867775838699, + "price": 46, + "amount": 35, "status": "open", "side": "sell", "user": "bob" @@ -339,23 +464,12 @@ application/json "transactionID": "0.40809971923762905", "value": { "id": 0.4453516187568676, - "price": 45.81208093633138, - "amount": 4.377739461490645, + "price": 46, + "amount": 5, "status": "open", "side": "sell", "user": "bob" } - }, - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.4453516187568676, - "price": 45.81208093633138, - "amount": 0, - "status": "sold", - "side": "sell", - "user": "bob" - } } ], "/trades/0.02466078710245042": [ @@ -365,8 +479,8 @@ application/json "id": 0.02466078710245042, "buyOrder": 0.5100409435201818, "sellOrder": 0.4453516187568676, - "amount": 57.34558464712052, - "price": 53.01054052823274 + "amount": 57, + "price": 53 } } ], @@ -375,8 +489,8 @@ application/json "transactionID": "0.6062274799740198", "value": { "id": 0.8652590050969823, - "price": 40.85953444322528, - "amount": 5.2026452545101245, + "price": 41, + "amount": 5, "side": "buy", "status": "open", "user": "alice" @@ -386,7 +500,7 @@ application/json "transactionID": "0.2855238856612927", "value": { "id": 0.8652590050969823, - "price": 40.85953444322528, + "price": 41, "amount": 0, "status": "sold", "side": "buy", @@ -399,23 +513,12 @@ application/json "transactionID": "0.8863366133406168", "value": { "id": 0.6594613051595737, - "price": 50.936840475478064, - "amount": 22.160465058088736, + "price": 51, + "amount": 22, "side": "sell", "status": "open", "user": "alice" } - }, - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.6594613051595737, - "price": 50.936840475478064, - "amount": 0, - "status": "sold", - "side": "sell", - "user": "alice" - } } ], "/orders/0.4738564640040104": [ @@ -423,8 +526,8 @@ application/json "transactionID": "0.02466078710245042", "value": { "id": 0.4738564640040104, - "price": 33.81343161111086, - "amount": 13.419040444697291, + "price": 34, + "amount": 13, "side": "buy", "status": "open", "user": "bob" @@ -434,7 +537,7 @@ application/json "transactionID": "0.2855238856612927", "value": { "id": 0.4738564640040104, - "price": 33.81343161111086, + "price": 34, "amount": 0, "status": "sold", "side": "buy", @@ -447,8 +550,8 @@ application/json "transactionID": "0.3051861941862723", "value": { "id": 0.2643711262050747, - "price": 43.23453176136551, - "amount": 3.453515426678132, + "price": 43, + "amount": 3, "side": "buy", "status": "open", "user": "bob" @@ -458,7 +561,7 @@ application/json "transactionID": "0.2855238856612927", "value": { "id": 0.2643711262050747, - "price": 43.23453176136551, + "price": 43, "amount": 0, "status": "sold", "side": "buy", @@ -471,8 +574,8 @@ application/json "transactionID": "0.40809971923762905", "value": { "id": 0.9319858592301475, - "price": 61.90427058553721, - "amount": 30.009038122379255, + "price": 62, + "amount": 30, "side": "buy", "status": "open", "user": "alice" @@ -482,7 +585,7 @@ application/json "transactionID": "0.40809971923762905", "value": { "id": 0.9319858592301475, - "price": 61.90427058553721, + "price": 62, "amount": 0, "status": "sold", "side": "buy", @@ -497,8 +600,8 @@ application/json "id": 0.8863366133406168, "buyOrder": 0.9319858592301475, "sellOrder": 0.4453516187568676, - "amount": 30.009038122379255, - "price": 53.858175760934294 + "amount": 30, + "price": 54 } } ], @@ -507,8 +610,8 @@ application/json "transactionID": "0.2855238856612927", "value": { "id": 0.7999519010073989, - "price": 26.76166373003429, - "amount": 62.511260865732346, + "price": 27, + "amount": 63, "side": "sell", "status": "open", "user": "bob" @@ -518,8 +621,8 @@ application/json "transactionID": "0.2855238856612927", "value": { "id": 0.7999519010073989, - "price": 26.76166373003429, - "amount": 59.057745439054216, + "price": 27, + "amount": 60, "status": "open", "side": "sell", "user": "bob" @@ -529,8 +632,8 @@ application/json "transactionID": "0.2855238856612927", "value": { "id": 0.7999519010073989, - "price": 26.76166373003429, - "amount": 57.30861561122222, + "price": 27, + "amount": 55, "status": "open", "side": "sell", "user": "bob" @@ -540,8 +643,8 @@ application/json "transactionID": "0.2855238856612927", "value": { "id": 0.7999519010073989, - "price": 26.76166373003429, - "amount": 49.09222042103505, + "price": 27, + "amount": 42, "status": "open", "side": "sell", "user": "bob" @@ -551,23 +654,12 @@ application/json "transactionID": "0.10559463277980186", "value": { "id": 0.7999519010073989, - "price": 26.76166373003429, - "amount": 8.353928996579661, + "price": 27, + "amount": 1, "status": "open", "side": "sell", "user": "bob" } - }, - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.7999519010073989, - "price": 26.76166373003429, - "amount": 0, - "status": "sold", - "side": "sell", - "user": "bob" - } } ], "/trades/0.7916017969060706": [ @@ -577,8 +669,8 @@ application/json "id": 0.7916017969060706, "buyOrder": 0.2643711262050747, "sellOrder": 0.7999519010073989, - "amount": 3.453515426678132, - "price": 34.9980977456999 + "amount": 3, + "price": 35 } } ], @@ -589,8 +681,8 @@ application/json "id": 0.45140223107431293, "buyOrder": 0.8652590050969823, "sellOrder": 0.7999519010073989, - "amount": 5.2026452545101245, - "price": 33.810599086629786 + "amount": 5, + "price": 34 } } ], @@ -601,8 +693,8 @@ application/json "id": 0.7173019593742694, "buyOrder": 0.4738564640040104, "sellOrder": 0.7999519010073989, - "amount": 13.419040444697291, - "price": 30.28754767057257 + "amount": 13, + "price": 30.5 } } ], @@ -611,8 +703,8 @@ application/json "transactionID": "0.10559463277980186", "value": { "id": 0.7290001299502329, - "price": 88.46443462042551, - "amount": 40.73829142445539, + "price": 88, + "amount": 41, "side": "buy", "status": "open", "user": "bob" @@ -622,7 +714,7 @@ application/json "transactionID": "0.10559463277980186", "value": { "id": 0.7290001299502329, - "price": 88.46443462042551, + "price": 88, "amount": 0, "status": "sold", "side": "buy", @@ -637,8 +729,8 @@ application/json "id": 0.3051861941862723, "buyOrder": 0.7290001299502329, "sellOrder": 0.7999519010073989, - "amount": 40.73829142445539, - "price": 57.6130491752299 + "amount": 41, + "price": 57.5 } } ], @@ -647,8 +739,8 @@ application/json "transactionID": "0.7916017969060706", "value": { "id": 0.45140223107431293, - "price": 18.17019928076323, - "amount": 66.58643243516464, + "price": 18, + "amount": 67, "side": "sell", "status": "open", "user": "bob" @@ -658,8 +750,8 @@ application/json "transactionID": "0.5661584819351868", "value": { "id": 0.45140223107431293, - "price": 18.17019928076323, - "amount": 55.41070527901008, + "price": 18, + "amount": 56, "status": "open", "side": "sell", "user": "bob" @@ -669,8 +761,8 @@ application/json "transactionID": "0.7173019593742694", "value": { "id": 0.45140223107431293, - "price": 18.17019928076323, - "amount": 28.678633997848856, + "price": 18, + "amount": 29, "status": "open", "side": "sell", "user": "bob" @@ -680,7 +772,7 @@ application/json "transactionID": "0.8601966894792362", "value": { "id": 0.45140223107431293, - "price": 18.17019928076323, + "price": 18, "amount": 0, "status": "sold", "side": "sell", @@ -693,8 +785,8 @@ application/json "transactionID": "0.5661584819351868", "value": { "id": 0.42560927982033164, - "price": 30.447008628814487, - "amount": 11.175727156154558, + "price": 30, + "amount": 11, "side": "buy", "status": "open", "user": "alice" @@ -704,7 +796,7 @@ application/json "transactionID": "0.5661584819351868", "value": { "id": 0.42560927982033164, - "price": 30.447008628814487, + "price": 30, "amount": 0, "status": "sold", "side": "buy", @@ -719,8 +811,8 @@ application/json "id": 0.21517043534216512, "buyOrder": 0.42560927982033164, "sellOrder": 0.45140223107431293, - "amount": 11.175727156154558, - "price": 24.308603954788857 + "amount": 11, + "price": 24 } } ], @@ -729,8 +821,8 @@ application/json "transactionID": "0.7173019593742694", "value": { "id": 0.694033415703134, - "price": 85.92259589202943, - "amount": 26.732071281161225, + "price": 86, + "amount": 27, "side": "buy", "status": "open", "user": "bob" @@ -740,7 +832,7 @@ application/json "transactionID": "0.7173019593742694", "value": { "id": 0.694033415703134, - "price": 85.92259589202943, + "price": 86, "amount": 0, "status": "sold", "side": "buy", @@ -755,8 +847,8 @@ application/json "id": 0.619620117004607, "buyOrder": 0.694033415703134, "sellOrder": 0.45140223107431293, - "amount": 26.732071281161225, - "price": 52.04639758639633 + "amount": 27, + "price": 52 } } ], @@ -765,8 +857,8 @@ application/json "transactionID": "0.619620117004607", "value": { "id": 0.9553094734021551, - "price": 9.207850982628624, - "amount": 7.188029221434173, + "price": 9, + "amount": 7, "side": "sell", "status": "open", "user": "bob" @@ -776,7 +868,7 @@ application/json "transactionID": "0.619620117004607", "value": { "id": 0.9553094734021551, - "price": 9.207850982628624, + "price": 9, "amount": 0, "status": "sold", "side": "sell", @@ -791,8 +883,8 @@ application/json "id": 0.8863198197319357, "buyOrder": 0.015206331866985496, "sellOrder": 0.9553094734021551, - "amount": 7.188029221434173, - "price": 13.068871864181824 + "amount": 7, + "price": 13 } } ], @@ -801,23 +893,12 @@ application/json "transactionID": "0.8863198197319357", "value": { "id": 0.37721112405621554, - "price": 45.103340684532505, - "amount": 40.76546099108221, + "price": 45, + "amount": 41, "side": "sell", "status": "open", "user": "bob" } - }, - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.37721112405621554, - "price": 45.103340684532505, - "amount": 0, - "status": "sold", - "side": "sell", - "user": "bob" - } } ], "/orders/0.3695129378414833": [ @@ -825,23 +906,12 @@ application/json "transactionID": "0.21517043534216512", "value": { "id": 0.3695129378414833, - "price": 54.946238738434616, - "amount": 38.136190025262714, + "price": 55, + "amount": 38, "side": "sell", "status": "open", "user": "alice" } - }, - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.3695129378414833, - "price": 54.946238738434616, - "amount": 0, - "status": "sold", - "side": "sell", - "user": "alice" - } } ], "/orders/0.20842740052233208": [ @@ -849,8 +919,8 @@ application/json "transactionID": "0.4039512359573983", "value": { "id": 0.20842740052233208, - "price": 18.209919676380157, - "amount": 53.90124409822863, + "price": 18, + "amount": 54, "side": "sell", "status": "open", "user": "alice" @@ -860,7 +930,18 @@ application/json "transactionID": "0.8601966894792362", "value": { "id": 0.20842740052233208, - "price": 18.209919676380157, + "price": 18, + "amount": 35, + "status": "open", + "side": "sell", + "user": "alice" + } + }, + { + "transactionID": "0.06801234424860435", + "value": { + "id": 0.20842740052233208, + "price": 18, "amount": 0, "status": "sold", "side": "sell", @@ -873,23 +954,12 @@ application/json "transactionID": "0.03932677352738266", "value": { "id": 0.9650901932875534, - "price": 44.04468768652965, - "amount": 65.18974501191614, + "price": 44, + "amount": 65, "side": "sell", "status": "open", "user": "alice" } - }, - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.9650901932875534, - "price": 44.04468768652965, - "amount": 0, - "status": "sold", - "side": "sell", - "user": "alice" - } } ], "/orders/0.6609517467775864": [ @@ -897,23 +967,12 @@ application/json "transactionID": "0.27087885678827656", "value": { "id": 0.6609517467775864, - "price": 53.02912770121277, - "amount": 25.849528681346705, + "price": 53, + "amount": 26, "side": "sell", "status": "open", "user": "alice" } - }, - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.6609517467775864, - "price": 53.02912770121277, - "amount": 0, - "status": "sold", - "side": "sell", - "user": "alice" - } } ], "/orders/0.2752696380720191": [ @@ -921,8 +980,8 @@ application/json "transactionID": "0.787366886890835", "value": { "id": 0.2752696380720191, - "price": 10.674027689429026, - "amount": 34.3256187013589, + "price": 11, + "amount": 34, "side": "sell", "status": "open", "user": "bob" @@ -932,7 +991,7 @@ application/json "transactionID": "0.787366886890835", "value": { "id": 0.2752696380720191, - "price": 10.674027689429026, + "price": 11, "amount": 0, "status": "sold", "side": "sell", @@ -947,8 +1006,8 @@ application/json "id": 0.4568127481795966, "buyOrder": 0.015206331866985496, "sellOrder": 0.2752696380720191, - "amount": 34.3256187013589, - "price": 13.801960217582025 + "amount": 34, + "price": 14 } } ], @@ -957,8 +1016,8 @@ application/json "transactionID": "0.8598554999193694", "value": { "id": 0.5913882414730156, - "price": 5.453274869782175, - "amount": 70.84580550049041, + "price": 5, + "amount": 71, "side": "sell", "status": "open", "user": "bob" @@ -968,8 +1027,8 @@ application/json "transactionID": "0.8598554999193694", "value": { "id": 0.5913882414730156, - "price": 5.453274869782175, - "amount": 37.9737047366553, + "price": 5, + "amount": 27, "status": "open", "side": "sell", "user": "bob" @@ -979,8 +1038,8 @@ application/json "transactionID": "0.4568127481795966", "value": { "id": 0.5913882414730156, - "price": 5.453274869782175, - "amount": 31.72038945529646, + "price": 5, + "amount": 21, "status": "open", "side": "sell", "user": "bob" @@ -990,8 +1049,8 @@ application/json "transactionID": "0.46217763466963324", "value": { "id": 0.5913882414730156, - "price": 5.453274869782175, - "amount": 17.41572694612269, + "price": 5, + "amount": 7, "status": "open", "side": "sell", "user": "bob" @@ -1001,20 +1060,9 @@ application/json "transactionID": "0.6953998633617534", "value": { "id": 0.5913882414730156, - "price": 5.453274869782175, - "amount": 8.517618438711033, - "status": "open", - "side": "sell", - "user": "bob" - } - }, - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.5913882414730156, - "price": 5.453274869782175, - "amount": 0, - "status": "sold", + "price": 5, + "amount": 0, + "status": "sold", "side": "sell", "user": "bob" } @@ -1027,8 +1075,8 @@ application/json "id": 0.46217763466963324, "buyOrder": 0.015206331866985496, "sellOrder": 0.5913882414730156, - "amount": 32.872100763835114, - "price": 11.1915838077586 + "amount": 44, + "price": 11 } } ], @@ -1037,8 +1085,8 @@ application/json "transactionID": "0.4568127481795966", "value": { "id": 0.6518629055021916, - "price": 99.47066749396795, - "amount": 6.253315281358842, + "price": 99, + "amount": 6, "side": "buy", "status": "open", "user": "bob" @@ -1048,7 +1096,7 @@ application/json "transactionID": "0.4568127481795966", "value": { "id": 0.6518629055021916, - "price": 99.47066749396795, + "price": 99, "amount": 0, "status": "sold", "side": "buy", @@ -1063,8 +1111,8 @@ application/json "id": 0.8598554999193694, "buyOrder": 0.6518629055021916, "sellOrder": 0.5913882414730156, - "amount": 6.253315281358842, - "price": 52.461971181875064 + "amount": 6, + "price": 52 } } ], @@ -1073,20 +1121,20 @@ application/json "transactionID": "0.6160107442326944", "value": { "id": 0.29258132380673785, - "price": 21.071714974075288, - "amount": 71.29892732137715, + "price": 21, + "amount": 71, "side": "sell", "status": "open", "user": "alice" } }, { - "transactionID": "0.8601966894792362", + "transactionID": "0.06801234424860435", "value": { "id": 0.29258132380673785, - "price": 21.071714974075288, - "amount": 0, - "status": "sold", + "price": 21, + "amount": 22, + "status": "open", "side": "sell", "user": "alice" } @@ -1097,8 +1145,8 @@ application/json "transactionID": "0.46217763466963324", "value": { "id": 0.8195101016382781, - "price": 18.463462328969932, - "amount": 14.30466250917377, + "price": 18, + "amount": 14, "side": "buy", "status": "open", "user": "bob" @@ -1108,7 +1156,7 @@ application/json "transactionID": "0.46217763466963324", "value": { "id": 0.8195101016382781, - "price": 18.463462328969932, + "price": 18, "amount": 0, "status": "sold", "side": "buy", @@ -1123,8 +1171,8 @@ application/json "id": 0.5062796468904983, "buyOrder": 0.8195101016382781, "sellOrder": 0.5913882414730156, - "amount": 14.30466250917377, - "price": 11.958368599376053 + "amount": 14, + "price": 11.5 } } ], @@ -1133,23 +1181,12 @@ application/json "transactionID": "0.4143147560901146", "value": { "id": 0.3881101900600923, - "price": 63.037587481585874, - "amount": 57.545251220041195, + "price": 63, + "amount": 58, "side": "sell", "status": "open", "user": "alice" } - }, - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.3881101900600923, - "price": 63.037587481585874, - "amount": 0, - "status": "sold", - "side": "sell", - "user": "alice" - } } ], "/orders/0.04202915219779047": [ @@ -1157,18 +1194,29 @@ application/json "transactionID": "0.5062796468904983", "value": { "id": 0.04202915219779047, - "price": 14.714031819919155, - "amount": 26.07334524027383, + "price": 15, + "amount": 26, "side": "sell", "status": "open", "user": "bob" } }, + { + "transactionID": "0.6953998633617534", + "value": { + "id": 0.04202915219779047, + "price": 15, + "amount": 24, + "status": "open", + "side": "sell", + "user": "bob" + } + }, { "transactionID": "0.8601966894792362", "value": { "id": 0.04202915219779047, - "price": 14.714031819919155, + "price": 15, "amount": 0, "status": "sold", "side": "sell", @@ -1181,8 +1229,8 @@ application/json "transactionID": "0.6057206938096514", "value": { "id": 0.34770394428419316, - "price": 85.3441100896747, - "amount": 68.30995084560472, + "price": 85, + "amount": 68, "side": "sell", "status": "open", "user": "bob" @@ -1194,8 +1242,8 @@ application/json "transactionID": "0.3839684854112272", "value": { "id": 0.3583391274868875, - "price": 0.08381153464672299, - "amount": 35.15202494817974, + "price": 0, + "amount": 35, "side": "buy", "status": "open", "user": "bob" @@ -1207,23 +1255,12 @@ application/json "transactionID": "0.9679691288321923", "value": { "id": 0.6571485331814256, - "price": 59.59723588041703, - "amount": 38.92185379650616, + "price": 60, + "amount": 39, "side": "sell", "status": "open", "user": "alice" } - }, - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.6571485331814256, - "price": 59.59723588041703, - "amount": 0, - "status": "sold", - "side": "sell", - "user": "alice" - } } ], "/orders/0.5855059047094602": [ @@ -1231,8 +1268,8 @@ application/json "transactionID": "0.6953998633617534", "value": { "id": 0.5855059047094602, - "price": 50.51039704169183, - "amount": 8.898108507411656, + "price": 51, + "amount": 9, "side": "buy", "status": "open", "user": "alice" @@ -1242,7 +1279,18 @@ application/json "transactionID": "0.6953998633617534", "value": { "id": 0.5855059047094602, - "price": 50.51039704169183, + "price": 51, + "amount": 2, + "status": "open", + "side": "buy", + "user": "alice" + } + }, + { + "transactionID": "0.6953998633617534", + "value": { + "id": 0.5855059047094602, + "price": 51, "amount": 0, "status": "sold", "side": "buy", @@ -1257,141 +1305,32 @@ application/json "id": 0.5977436956928519, "buyOrder": 0.5855059047094602, "sellOrder": 0.5913882414730156, - "amount": 8.898108507411656, - "price": 27.981835955737004 + "amount": 7, + "price": 28 } } ], - "/orders/0.3257611713612091": [ + "/trades/0.27829665763145": [ { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.3257611713612091, - "price": 82.06709235167791, - "amount": 72.08794352792943, - "side": "buy", - "status": "open", - "user": "bob" - } - }, - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.3257611713612091, - "price": 82.06709235167791, - "amount": 63.57032508921839, - "status": "open", - "side": "buy", - "user": "bob" - } - }, - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.3257611713612091, - "price": 82.06709235167791, - "amount": 46.014598287655595, - "status": "open", - "side": "buy", - "user": "bob" - } - }, - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.3257611713612091, - "price": 82.06709235167791, - "amount": 43.40930953008057, - "status": "open", - "side": "buy", - "user": "bob" - } - }, - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.3257611713612091, - "price": 82.06709235167791, - "amount": 18.186699429700795, - "status": "open", - "side": "buy", - "user": "bob" - } - }, - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.3257611713612091, - "price": 82.06709235167791, - "amount": 0.7890162065522759, - "status": "open", - "side": "buy", - "user": "bob" - } - }, - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.3257611713612091, - "price": 82.06709235167791, - "amount": 63.734014531349764, - "status": "open", - "side": "buy", - "user": "bob" - } - }, - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.3257611713612091, - "price": 82.06709235167791, - "amount": 6.898198516013281, - "status": "open", - "side": "buy", - "user": "bob" - } - }, - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.3257611713612091, - "price": 82.06709235167791, - "amount": 31.322482536847218, - "status": "open", - "side": "buy", - "user": "bob" - } - }, - { - "transactionID": "0.8601966894792362", + "transactionID": "0.6953998633617534", "value": { - "id": 0.3257611713612091, - "price": 82.06709235167791, - "amount": 67.71020406643878, - "status": "open", - "side": "buy", - "user": "bob" + "id": 0.27829665763145, + "buyOrder": 0.5855059047094602, + "sellOrder": 0.04202915219779047, + "amount": 2, + "price": 33 } - }, + } + ], + "/orders/0.3257611713612091": [ { "transactionID": "0.8601966894792362", "value": { "id": 0.3257611713612091, - "price": 82.06709235167791, - "amount": 49.92747846984069, - "status": "open", + "price": 82, + "amount": 72, "side": "buy", - "user": "bob" - } - }, - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.3257611713612091, - "price": 82.06709235167791, - "amount": 46.23841484658272, "status": "open", - "side": "buy", "user": "bob" } }, @@ -1399,8 +1338,8 @@ application/json "transactionID": "0.8601966894792362", "value": { "id": 0.3257611713612091, - "price": 82.06709235167791, - "amount": 33.95175350266671, + "price": 82, + "amount": 48, "status": "open", "side": "buy", "user": "bob" @@ -1410,8 +1349,8 @@ application/json "transactionID": "0.8601966894792362", "value": { "id": 0.3257611713612091, - "price": 82.06709235167791, - "amount": 33.16608973142326, + "price": 82, + "amount": 19, "status": "open", "side": "buy", "user": "bob" @@ -1421,9 +1360,9 @@ application/json "transactionID": "0.8601966894792362", "value": { "id": 0.3257611713612091, - "price": 82.06709235167791, - "amount": 14.542692307888231, - "status": "open", + "price": 82, + "amount": 0, + "status": "sold", "side": "buy", "user": "bob" } @@ -1435,9 +1374,9 @@ application/json "value": { "id": 0.06801234424860435, "buyOrder": 0.3257611713612091, - "sellOrder": 0.5913882414730156, - "amount": 8.517618438711033, - "price": 43.760183610730046 + "sellOrder": 0.04202915219779047, + "amount": 24, + "price": 48.5 } } ], @@ -1447,9 +1386,9 @@ application/json "value": { "id": 0.08347707994606074, "buyOrder": 0.3257611713612091, - "sellOrder": 0.04202915219779047, - "amount": 26.07334524027383, - "price": 48.390562085798535 + "sellOrder": 0.45140223107431293, + "amount": 29, + "price": 50 } } ], @@ -1459,167 +1398,79 @@ application/json "value": { "id": 0.9992898264893236, "buyOrder": 0.3257611713612091, - "sellOrder": 0.45140223107431293, - "amount": 28.678633997848856, - "price": 50.11864581622057 - } - } - ], - "/trades/0.06411381118382682": [ - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.06411381118382682, - "buyOrder": 0.3257611713612091, "sellOrder": 0.20842740052233208, - "amount": 53.90124409822863, - "price": 50.13850601402903 - } - } - ], - "/trades/0.5608318909637927": [ - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.5608318909637927, - "buyOrder": 0.3257611713612091, - "sellOrder": 0.29258132380673785, - "amount": 71.29892732137715, - "price": 51.5694036628766 - } - } - ], - "/trades/0.9015948655098629": [ - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.9015948655098629, - "buyOrder": 0.3257611713612091, - "sellOrder": 0.7999519010073989, - "amount": 8.353928996579661, - "price": 54.4143780408561 - } - } - ], - "/trades/0.10490539400363844": [ - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.10490539400363844, - "buyOrder": 0.3257611713612091, - "sellOrder": 0.9650901932875534, - "amount": 65.18974501191614, - "price": 63.055890019103785 - } - } - ], - "/trades/0.14496402409389988": [ - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.14496402409389988, - "buyOrder": 0.3257611713612091, - "sellOrder": 0.37721112405621554, - "amount": 40.76546099108221, - "price": 63.58521651810521 - } - } - ], - "/trades/0.41035963772829587": [ - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.41035963772829587, - "buyOrder": 0.3257611713612091, - "sellOrder": 0.4453516187568676, - "amount": 4.377739461490645, - "price": 63.93958664400465 + "amount": 19, + "price": 50 } - } - ], - "/trades/0.9144359141722656": [ - { - "transactionID": "0.8601966894792362", - "value": { - "id": 0.9144359141722656, - "buyOrder": 0.3257611713612091, - "sellOrder": 0.6594613051595737, - "amount": 22.160465058088736, - "price": 66.50196641357799 - } - } - ], - "/trades/0.9244101628888493": [ + }, { - "transactionID": "0.8601966894792362", + "transactionID": "0.06801234424860435", "value": { - "id": 0.9244101628888493, - "buyOrder": 0.3257611713612091, - "sellOrder": 0.6609517467775864, - "amount": 25.849528681346705, - "price": 67.54811002644534 + "id": 0.9992898264893236, + "buyOrder": 0.08347707994606074, + "sellOrder": 0.20842740052233208, + "amount": 35, + "price": 35 } } ], - "/trades/0.5616082642801183": [ + "/orders/0.06411381118382682": [ { - "transactionID": "0.8601966894792362", + "transactionID": "0.9992898264893236", "value": { - "id": 0.5616082642801183, - "buyOrder": 0.3257611713612091, - "sellOrder": 0.3695129378414833, - "amount": 38.136190025262714, - "price": 68.50666554505626 + "id": 0.06411381118382682, + "price": 15, + "amount": 73, + "side": "buy", + "status": "open", + "user": "bob" } } ], - "/trades/0.9501011869405407": [ + "/orders/0.08347707994606074": [ { - "transactionID": "0.8601966894792362", + "transactionID": "0.06801234424860435", "value": { - "id": 0.9501011869405407, - "buyOrder": 0.3257611713612091, - "sellOrder": 0.6571485331814256, - "amount": 38.92185379650616, - "price": 70.83216411604747 + "id": 0.08347707994606074, + "price": 52, + "amount": 84, + "side": "buy", + "status": "open", + "user": "bob" } - } - ], - "/trades/0.3506492998922703": [ + }, { - "transactionID": "0.8601966894792362", + "transactionID": "0.06801234424860435", "value": { - "id": 0.3506492998922703, - "buyOrder": 0.3257611713612091, - "sellOrder": 0.3881101900600923, - "amount": 57.545251220041195, - "price": 72.5523399166319 + "id": 0.08347707994606074, + "price": 52, + "amount": 49, + "status": "open", + "side": "buy", + "user": "bob" } - } - ], - "/orders/0.06411381118382682": [ + }, { - "transactionID": "0.9992898264893236", + "transactionID": "0.06801234424860435", "value": { - "id": 0.06411381118382682, - "price": 15.311881075903663, - "amount": 72.79795987792123, + "id": 0.08347707994606074, + "price": 52, + "amount": 0, + "status": "sold", "side": "buy", - "status": "open", "user": "bob" } } ], - "/orders/0.08347707994606074": [ + "/trades/0.06411381118382682": [ { "transactionID": "0.06801234424860435", "value": { - "id": 0.08347707994606074, - "price": 51.974749287566866, - "amount": 84.354850635263, - "side": "buy", - "status": "open", - "user": "bob" + "id": 0.06411381118382682, + "buyOrder": 0.08347707994606074, + "sellOrder": 0.29258132380673785, + "amount": 49, + "price": 36.5 } } ] @@ -1631,8 +1482,8 @@ application/json "transactionID": "0.00037566525896607457", "value": { "id": 0.31381383055244927, - "price": 37.316768604625736, - "amount": 20.142637957020327, + "price": 37, + "amount": 20, "side": "sell", "status": "open", "user": "alice" @@ -1644,8 +1495,8 @@ application/json "transactionID": "0.26905546502122235", "value": { "id": 0.015206331866985496, - "price": 16.929892745735025, - "amount": 85.09055332754791, + "price": 17, + "amount": 85, "side": "buy", "status": "open", "user": "alice" @@ -1657,8 +1508,8 @@ application/json "transactionID": "0.5728273955852048", "value": { "id": 0.5100409435201818, - "price": 60.20900012013409, - "amount": 88.19302724506056, + "price": 60, + "amount": 88, "side": "buy", "status": "open", "user": "alice" @@ -1670,8 +1521,8 @@ application/json "transactionID": "0.2581415779498793", "value": { "id": 0.5855064094862961, - "price": 15.652298755619952, - "amount": 10.70480464091972, + "price": 16, + "amount": 11, "side": "sell", "status": "open", "user": "alice" @@ -1683,8 +1534,8 @@ application/json "transactionID": "0.6062274799740198", "value": { "id": 0.8652590050969823, - "price": 40.85953444322528, - "amount": 5.2026452545101245, + "price": 41, + "amount": 5, "side": "buy", "status": "open", "user": "alice" @@ -1696,8 +1547,8 @@ application/json "transactionID": "0.40809971923762905", "value": { "id": 0.9319858592301475, - "price": 61.90427058553721, - "amount": 30.009038122379255, + "price": 62, + "amount": 30, "side": "buy", "status": "open", "user": "alice" @@ -1709,8 +1560,8 @@ application/json "transactionID": "0.8863366133406168", "value": { "id": 0.6594613051595737, - "price": 50.936840475478064, - "amount": 22.160465058088736, + "price": 51, + "amount": 22, "side": "sell", "status": "open", "user": "alice" @@ -1722,8 +1573,8 @@ application/json "transactionID": "0.5661584819351868", "value": { "id": 0.42560927982033164, - "price": 30.447008628814487, - "amount": 11.175727156154558, + "price": 30, + "amount": 11, "side": "buy", "status": "open", "user": "alice" @@ -1735,8 +1586,8 @@ application/json "transactionID": "0.21517043534216512", "value": { "id": 0.3695129378414833, - "price": 54.946238738434616, - "amount": 38.136190025262714, + "price": 55, + "amount": 38, "side": "sell", "status": "open", "user": "alice" @@ -1748,8 +1599,8 @@ application/json "transactionID": "0.4039512359573983", "value": { "id": 0.20842740052233208, - "price": 18.209919676380157, - "amount": 53.90124409822863, + "price": 18, + "amount": 54, "side": "sell", "status": "open", "user": "alice" @@ -1761,8 +1612,8 @@ application/json "transactionID": "0.03932677352738266", "value": { "id": 0.9650901932875534, - "price": 44.04468768652965, - "amount": 65.18974501191614, + "price": 44, + "amount": 65, "side": "sell", "status": "open", "user": "alice" @@ -1774,8 +1625,8 @@ application/json "transactionID": "0.27087885678827656", "value": { "id": 0.6609517467775864, - "price": 53.02912770121277, - "amount": 25.849528681346705, + "price": 53, + "amount": 26, "side": "sell", "status": "open", "user": "alice" @@ -1787,8 +1638,8 @@ application/json "transactionID": "0.6160107442326944", "value": { "id": 0.29258132380673785, - "price": 21.071714974075288, - "amount": 71.29892732137715, + "price": 21, + "amount": 71, "side": "sell", "status": "open", "user": "alice" @@ -1800,8 +1651,8 @@ application/json "transactionID": "0.4143147560901146", "value": { "id": 0.3881101900600923, - "price": 63.037587481585874, - "amount": 57.545251220041195, + "price": 63, + "amount": 58, "side": "sell", "status": "open", "user": "alice" @@ -1813,8 +1664,8 @@ application/json "transactionID": "0.9679691288321923", "value": { "id": 0.6571485331814256, - "price": 59.59723588041703, - "amount": 38.92185379650616, + "price": 60, + "amount": 39, "side": "sell", "status": "open", "user": "alice" @@ -1826,8 +1677,8 @@ application/json "transactionID": "0.6953998633617534", "value": { "id": 0.5855059047094602, - "price": 50.51039704169183, - "amount": 8.898108507411656, + "price": 51, + "amount": 9, "side": "buy", "status": "open", "user": "alice" @@ -1842,8 +1693,8 @@ application/json "transactionID": "0.0003834916282291446", "value": { "id": 0.4453516187568676, - "price": 45.81208093633138, - "amount": 91.73236223099042, + "price": 46, + "amount": 92, "side": "sell", "status": "open", "user": "bob" @@ -1855,8 +1706,8 @@ application/json "transactionID": "0.02466078710245042", "value": { "id": 0.4738564640040104, - "price": 33.81343161111086, - "amount": 13.419040444697291, + "price": 34, + "amount": 13, "side": "buy", "status": "open", "user": "bob" @@ -1868,8 +1719,8 @@ application/json "transactionID": "0.10559463277980186", "value": { "id": 0.7290001299502329, - "price": 88.46443462042551, - "amount": 40.73829142445539, + "price": 88, + "amount": 41, "side": "buy", "status": "open", "user": "bob" @@ -1881,8 +1732,8 @@ application/json "transactionID": "0.3051861941862723", "value": { "id": 0.2643711262050747, - "price": 43.23453176136551, - "amount": 3.453515426678132, + "price": 43, + "amount": 3, "side": "buy", "status": "open", "user": "bob" @@ -1894,8 +1745,8 @@ application/json "transactionID": "0.2855238856612927", "value": { "id": 0.7999519010073989, - "price": 26.76166373003429, - "amount": 62.511260865732346, + "price": 27, + "amount": 63, "side": "sell", "status": "open", "user": "bob" @@ -1907,8 +1758,8 @@ application/json "transactionID": "0.7916017969060706", "value": { "id": 0.45140223107431293, - "price": 18.17019928076323, - "amount": 66.58643243516464, + "price": 18, + "amount": 67, "side": "sell", "status": "open", "user": "bob" @@ -1920,8 +1771,8 @@ application/json "transactionID": "0.7173019593742694", "value": { "id": 0.694033415703134, - "price": 85.92259589202943, - "amount": 26.732071281161225, + "price": 86, + "amount": 27, "side": "buy", "status": "open", "user": "bob" @@ -1933,8 +1784,8 @@ application/json "transactionID": "0.619620117004607", "value": { "id": 0.9553094734021551, - "price": 9.207850982628624, - "amount": 7.188029221434173, + "price": 9, + "amount": 7, "side": "sell", "status": "open", "user": "bob" @@ -1946,8 +1797,8 @@ application/json "transactionID": "0.8863198197319357", "value": { "id": 0.37721112405621554, - "price": 45.103340684532505, - "amount": 40.76546099108221, + "price": 45, + "amount": 41, "side": "sell", "status": "open", "user": "bob" @@ -1959,8 +1810,8 @@ application/json "transactionID": "0.787366886890835", "value": { "id": 0.2752696380720191, - "price": 10.674027689429026, - "amount": 34.3256187013589, + "price": 11, + "amount": 34, "side": "sell", "status": "open", "user": "bob" @@ -1972,8 +1823,8 @@ application/json "transactionID": "0.4568127481795966", "value": { "id": 0.6518629055021916, - "price": 99.47066749396795, - "amount": 6.253315281358842, + "price": 99, + "amount": 6, "side": "buy", "status": "open", "user": "bob" @@ -1985,8 +1836,8 @@ application/json "transactionID": "0.8598554999193694", "value": { "id": 0.5913882414730156, - "price": 5.453274869782175, - "amount": 70.84580550049041, + "price": 5, + "amount": 71, "side": "sell", "status": "open", "user": "bob" @@ -1998,8 +1849,8 @@ application/json "transactionID": "0.46217763466963324", "value": { "id": 0.8195101016382781, - "price": 18.463462328969932, - "amount": 14.30466250917377, + "price": 18, + "amount": 14, "side": "buy", "status": "open", "user": "bob" @@ -2011,8 +1862,8 @@ application/json "transactionID": "0.5062796468904983", "value": { "id": 0.04202915219779047, - "price": 14.714031819919155, - "amount": 26.07334524027383, + "price": 15, + "amount": 26, "side": "sell", "status": "open", "user": "bob" @@ -2024,8 +1875,8 @@ application/json "transactionID": "0.3839684854112272", "value": { "id": 0.3583391274868875, - "price": 0.08381153464672299, - "amount": 35.15202494817974, + "price": 0, + "amount": 35, "side": "buy", "status": "open", "user": "bob" @@ -2037,8 +1888,8 @@ application/json "transactionID": "0.6057206938096514", "value": { "id": 0.34770394428419316, - "price": 85.3441100896747, - "amount": 68.30995084560472, + "price": 85, + "amount": 68, "side": "sell", "status": "open", "user": "bob" @@ -2050,8 +1901,8 @@ application/json "transactionID": "0.8601966894792362", "value": { "id": 0.3257611713612091, - "price": 82.06709235167791, - "amount": 72.08794352792943, + "price": 82, + "amount": 72, "side": "buy", "status": "open", "user": "bob" @@ -2063,8 +1914,8 @@ application/json "transactionID": "0.06801234424860435", "value": { "id": 0.08347707994606074, - "price": 51.974749287566866, - "amount": 84.354850635263, + "price": 52, + "amount": 84, "side": "buy", "status": "open", "user": "bob" @@ -2076,8 +1927,8 @@ application/json "transactionID": "0.9992898264893236", "value": { "id": 0.06411381118382682, - "price": 15.311881075903663, - "amount": 72.79795987792123, + "price": 15, + "amount": 73, "side": "buy", "status": "open", "user": "bob" diff --git a/apps/actors/systems/kvSync/examples/commodityMarket/index.ts b/apps/actors/systems/kvSync/examples/commodityMarket/index.ts new file mode 100644 index 00000000..95d41d8d --- /dev/null +++ b/apps/actors/systems/kvSync/examples/commodityMarket/index.ts @@ -0,0 +1,17 @@ +import { KVApp } from "../types"; +import { choose } from "./choose"; +import { matchOrders, mutations } from "./mutations"; +import { MarketUI } from "./ui"; + +export const commodityMarket: KVApp = { + name: "Commodity Market", + mutations, + ui: MarketUI, + triggers: [ + { + prefix: "/orders/", + fn: matchOrders, + }, + ], + choose, +}; diff --git a/apps/actors/systems/kvSync/examples/commodityMarket/mutations.ts b/apps/actors/systems/kvSync/examples/commodityMarket/mutations.ts new file mode 100644 index 00000000..8b80035c --- /dev/null +++ b/apps/actors/systems/kvSync/examples/commodityMarket/mutations.ts @@ -0,0 +1,86 @@ +import { MutationCtx, TSMutationDefns, WriteOp } from "../../types"; +import { Order, readOrder } from "./types"; + +export const mutations: TSMutationDefns = { + Order: (ctx, [price, amount, side]) => { + const id = ctx.rand(); + ctx.write(`/orders/${id}`, { + id, + price, + amount, + side, + status: "open", + user: ctx.curUser, + }); + }, +}; + +export function matchOrders(ctx: MutationCtx, evt: WriteOp) { + // Prevent us from going on forever + if (evt.desc.type !== "Insert") { + return; + } + + const orders = ctx.scan("/orders/").map(readOrder); + + const buys = orders + .filter((order) => order.side === "buy" && order.status === "open") + .sort((a, b) => b.price - a.price); + const sells = orders + .filter((order) => order.side === "sell" && order.status === "open") + .sort((a, b) => a.price - b.price); + + for (const buy of buys) { + // TODO: keep buying while there's more to buy + for (const sell of sells) { + if (sell.status === "sold") { + continue; + } + + if (buy.price >= sell.price) { + const amount = Math.min(buy.amount, sell.amount); + const price = (buy.price + sell.price) / 2; + + // Execute the trade + const newBuyAmount = buy.amount - amount; + const newBuyStatus = newBuyAmount === 0 ? "sold" : "open"; + const newBuy: Order = { + ...buy, + amount: newBuyAmount, + status: newBuyStatus, + }; + ctx.write(`/orders/${buy.id}`, newBuy); + + const newSellAmount = sell.amount - amount; + const newSellStatus = newSellAmount === 0 ? "sold" : "open"; + const newSell: Order = { + ...sell, + amount: newSellAmount, + status: newSellStatus, + }; + ctx.write(`/orders/${sell.id}`, newSell); + + // for future loops + buy.amount = newBuyAmount; + buy.status = newBuyStatus; + sell.amount = newSellAmount; + sell.status = newSellStatus; + + // console.log("matchOrders", { newBuy, newSell }); + + const tradeID = ctx.rand(); + ctx.write(`/trades/${tradeID}`, { + id: tradeID, + buyOrder: buy.id, + sellOrder: sell.id, + amount, + price, + }); + + if (newBuyAmount === 0) { + break; + } + } + } + } +} diff --git a/apps/actors/systems/kvSync/examples/commodityMarket/types.ts b/apps/actors/systems/kvSync/examples/commodityMarket/types.ts new file mode 100644 index 00000000..39214dc6 --- /dev/null +++ b/apps/actors/systems/kvSync/examples/commodityMarket/types.ts @@ -0,0 +1,48 @@ +import { Json } from "../../../../../../util/json"; +import { TransactionState } from "../../client"; + +export type OfferStatus = "open" | "sold"; + +export type OrderSide = "sell" | "buy"; + +export type Order = { + id: number; + price: number; + amount: number; + side: OrderSide; + user: string; + status: OfferStatus; +}; + +export function readOrder(rawOrder: Json): Order { + const order = rawOrder as any; + return { + id: order.id as number, + price: order.price as number, + amount: order.amount as number, + status: order.status as OfferStatus, + side: order.side as OrderSide, + user: order.user as string, + }; +} + +export type Trade = { + id: number; + price: number; + amount: number; + buyOrder: number; + sellOrder: number; +}; + +export function readTrade(rawTrade: Json): Trade { + const trade = rawTrade as any; + return { + id: trade.id as number, + price: trade.price as number, + amount: trade.amount as number, + buyOrder: trade.buyOrder as number, + sellOrder: trade.sellOrder as number, + }; +} + +export type OrderWithState = Order & { state: TransactionState }; diff --git a/apps/actors/systems/kvSync/examples/commodityMarket/ui.tsx b/apps/actors/systems/kvSync/examples/commodityMarket/ui.tsx new file mode 100644 index 00000000..05eee140 --- /dev/null +++ b/apps/actors/systems/kvSync/examples/commodityMarket/ui.tsx @@ -0,0 +1,170 @@ +import React, { useState } from "react"; +import { UIProps } from "../../../../types"; +import { ClientState, QueryStatus } from "../../client"; +import { Client, makeClient, useLiveQuery } from "../../hooks"; +import { UserInput } from "../../types"; +import { LoginWrapper } from "../../uiCommon/loginWrapper"; +import { LoggedInHeader } from "../../uiCommon/loggedInHeader"; +import { Table } from "../../../../../../uiCommon/generic/table"; +import { + Order, + OrderSide, + OrderWithState, + readOrder, + readTrade, + Trade, +} from "./types"; +import { Inspector } from "../../uiCommon/inspector"; + +export function MarketUI(props: UIProps) { + const client = makeClient(props); + + return ( + } + /> + ); +} + +function MarketInner(props: { client: Client; user: string }) { + const [orders, orderQueryStatus] = useOrders(props.client); + const [trades, tradeQueryStatus] = useTrades(props.client); + + return ( + <> + +

Market

+
+ +

Orders

+ + {orderQueryStatus === "Loading" ? ( + Loading... + ) : ( + + data={orders} + getKey={(order) => order.id.toString()} + columns={[ + { name: "id", render: (order) => order.id }, + { name: "price", render: (order) => `$${order.price}` }, + { name: "amount", render: (order) => order.amount }, + { name: "side", render: (order) => order.side }, + { name: "user", render: (order) => order.user }, + { name: "status", render: (order) => order.status }, + ]} + /> + )} + +

Create Order

+ + + +

Trades

+ + {tradeQueryStatus === "Loading" ? ( + Loading... + ) : ( + + data={trades} + getKey={(trade) => trade.id.toString()} + columns={[ + { name: "id", render: (trade) => trade.id }, + { name: "price", render: (trade) => `$${trade.price}` }, + { name: "amount", render: (trade) => trade.amount }, + { name: "buy order", render: (trade) => trade.buyOrder }, + { name: "sell order", render: (trade) => trade.sellOrder }, + ]} + /> + )} + + + + ); +} + +function OrderForm(props: { client: Client }) { + const [price, setPrice] = useState(10); + const [amount, setAmount] = useState(100); + const [side, setSide] = useState("buy"); + + return ( +
{ + evt.preventDefault(); + + props.client.runMutation("Order", [price, amount, side]); + }} + > + Amount:{" "} + setAmount(parseInt(evt.target.value))} + />{" "} + Price:{" "} + setPrice(parseInt(evt.target.value))} + /> +
+ + +
+ +
+ ); +} + +function useOrders(client: Client): [OrderWithState[], QueryStatus] { + const [rawOrders, queryStatus] = useLiveQuery(client, "list-orders", { + prefix: "/orders/", + }); + + const cmpKey = (a: OrderWithState) => (a.side === "buy" ? a.price : -a.price); + + const orders = Object.entries(rawOrders) + .map(([id, rawOrder]): OrderWithState => { + const order = rawOrder.value as any; + const mapped = readOrder(order); + return { + ...mapped, + state: client.state.transactions[rawOrder.transactionID]?.state, + }; + }) + .sort((a, b) => cmpKey(a) - cmpKey(b)); + + return [orders, queryStatus]; +} + +function useTrades(client: Client): [Trade[], QueryStatus] { + const [rawTrades, queryStatus] = useLiveQuery(client, "list-trades", { + prefix: "/trades/", + }); + + const trades = Object.entries(rawTrades).map(([id, rawTrade]): Trade => { + const trade = rawTrade.value as any; + return readTrade(trade); + }); + + return [trades, queryStatus]; +}