From 698617b97711edfaf17419d3c62ac327f20d36e0 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 30 Dec 2020 12:55:24 +0000 Subject: [PATCH 001/127] ported to nodejs --- websrc/.eslintcache | 1 + websrc/.gitignore | 24 +- websrc/README.md | 70 + websrc/components/Cards.tsx | 2 +- websrc/components/Currency.tsx | 45 - websrc/components/Events.tsx | 42 - websrc/components/RToast.tsx | 48 - websrc/package.json | 77 +- websrc/public/favicon.ico | Bin 0 -> 3870 bytes websrc/public/index.html | 43 + websrc/public/logo192.png | Bin 0 -> 5347 bytes websrc/public/logo512.png | Bin 0 -> 9664 bytes websrc/public/manifest.json | 25 + websrc/public/robots.txt | 3 + websrc/src/components/App.tsx | 131 + websrc/src/components/Cards.tsx | 154 + websrc/src/components/Currency.tsx | 9 + websrc/{ => src}/components/Icons.tsx | 2 +- websrc/{ => src}/components/Navbars.tsx | 22 +- websrc/{ => src}/components/Overlays.tsx | 2 +- websrc/{ => src}/components/RPlot.tsx | 74 +- websrc/{ => src}/components/Statusbar.tsx | 63 +- websrc/{ => src}/components/Tables.tsx | 34 +- websrc/{ => src}/css/.gitignore | 0 websrc/{ => src}/css/index.css | 0 websrc/src/custom.d.ts | 1 + websrc/{ => src}/index.tsx | 2 +- websrc/src/react-app-env.d.ts | 1 + websrc/{ => src}/types.ts | 7 +- websrc/{ => src}/utils.ts | 8 +- websrc/tsconfig.json | 37 +- websrc/yarn.lock | 7966 +++++++++++++++++---- 32 files changed, 7320 insertions(+), 1573 deletions(-) create mode 100644 websrc/.eslintcache create mode 100644 websrc/README.md delete mode 100644 websrc/components/Currency.tsx delete mode 100644 websrc/components/Events.tsx delete mode 100644 websrc/components/RToast.tsx create mode 100644 websrc/public/favicon.ico create mode 100644 websrc/public/index.html create mode 100644 websrc/public/logo192.png create mode 100644 websrc/public/logo512.png create mode 100644 websrc/public/manifest.json create mode 100644 websrc/public/robots.txt create mode 100644 websrc/src/components/App.tsx create mode 100644 websrc/src/components/Cards.tsx create mode 100644 websrc/src/components/Currency.tsx rename websrc/{ => src}/components/Icons.tsx (97%) rename websrc/{ => src}/components/Navbars.tsx (71%) rename websrc/{ => src}/components/Overlays.tsx (99%) rename websrc/{ => src}/components/RPlot.tsx (66%) rename websrc/{ => src}/components/Statusbar.tsx (81%) rename websrc/{ => src}/components/Tables.tsx (86%) rename websrc/{ => src}/css/.gitignore (100%) rename websrc/{ => src}/css/index.css (100%) create mode 100644 websrc/src/custom.d.ts rename websrc/{ => src}/index.tsx (90%) create mode 100644 websrc/src/react-app-env.d.ts rename websrc/{ => src}/types.ts (94%) rename websrc/{ => src}/utils.ts (79%) diff --git a/websrc/.eslintcache b/websrc/.eslintcache new file mode 100644 index 0000000..f238dc2 --- /dev/null +++ b/websrc/.eslintcache @@ -0,0 +1 @@ +[{"/home/giulio/dev/gkaching/websrc/src/components/App.tsx":"1","/home/giulio/dev/gkaching/websrc/src/components/Cards.tsx":"2","/home/giulio/dev/gkaching/websrc/src/components/Overlays.tsx":"3","/home/giulio/dev/gkaching/websrc/src/index.tsx":"4"},{"size":4418,"mtime":1609331626715,"results":"5","hashOfConfig":"6"},{"size":5688,"mtime":1609331022076,"results":"7","hashOfConfig":"6"},{"size":5235,"mtime":1609331232067,"results":"8","hashOfConfig":"6"},{"size":321,"mtime":1609332875579,"results":"9","hashOfConfig":"6"},{"filePath":"10","messages":"11","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"12"},"1ev2e5",{"filePath":"13","messages":"14","errorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"15"},{"filePath":"16","messages":"17","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"18"},{"filePath":"19","messages":"20","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/home/giulio/dev/gkaching/websrc/src/components/App.tsx",["21"],"import React, { Component } from \"react\";\nimport {\n Balance,\n CurrencyPair,\n EventName,\n EventProp,\n FirstConnectMessage,\n NewEventMessage,\n NewTickMessage,\n PositionProp\n} from \"../types\";\nimport { socket } from \"../index\";\nimport { symbolToPair } from \"../utils\";\nimport { Helmet } from \"react-helmet\";\nimport { Navbar, Sidebar } from \"./Navbars\";\nimport { Statusbar } from \"./Statusbar\";\nimport { PositionsTable } from \"./Tables\";\nimport RPlot from \"./RPlot\";\n\ntype AppState = {\n current_price: number,\n current_tick: number,\n last_update: Date,\n positions: Array,\n events: Array,\n active_pair: CurrencyPair,\n available_pairs: Array,\n balances: Array\n}\n\nclass App extends Component<{}, AppState> {\n event_id = 0;\n\n state = {\n current_price: 0,\n current_tick: 0,\n last_update: new Date(),\n positions: [],\n events: [],\n balances: [],\n active_pair: symbolToPair(\"tBTCUSD\"),\n available_pairs: []\n }\n\n constructor(props: {}) {\n super(props)\n }\n\n componentDidMount() {\n socket.on(EventName.FirstConnect, (data: FirstConnectMessage) => {\n this.setState({\n current_price: data.prices[data.prices.length - 1],\n current_tick: data.ticks[data.ticks.length - 1],\n last_update: new Date(),\n positions: data.positions,\n balances: data.balances\n })\n })\n\n socket.on(EventName.NewTick, (data: NewTickMessage) => {\n this.setState({\n current_price: data.price,\n current_tick: data.tick,\n last_update: new Date(),\n positions: data.positions,\n balances: data.balances\n })\n })\n\n socket.on(EventName.NewEvent, (data: NewEventMessage) => {\n // ignore new tick\n if (!data.kind.toLowerCase().includes(\"new_tick\")) {\n const new_event: EventProp = {\n id: this.event_id,\n name: data.kind,\n tick: data.tick\n }\n\n this.event_id += 1\n\n this.setState((state) => ({\n events: [...state.events, new_event]\n }))\n }\n })\n }\n\n render() {\n return (\n <>\n \n Rustico\n - {String(this.state.current_price.toLocaleString())} {String(this.state.active_pair.base) + \"/\" + String(this.state.active_pair.quote)} \n \n
\n
\n \n\n \n
\n \n
\n\n
\n \n \n
\n
\n\n {this.state.positions.length > 0 ?\n : null}\n\n
\n Made with ❤️ by the Peperone in a scantinato\n
\n \n\n \n
\n \n \n )\n }\n}\n\nexport default App;","/home/giulio/dev/gkaching/websrc/src/components/Cards.tsx",["22","23","24","25"],"import React, { Component } from 'react';\nimport { Balance, EventName, FirstConnectMessage, NewTickMessage } from \"../types\";\nimport { socket } from \"../index\";\n\nexport type CoinBalanceProps = {\n name: string,\n amount: number,\n percentage: number,\n quote_equivalent: number,\n quote_symbol: string,\n}\n\nclass CoinBalance extends Component {\n constructor(props: CoinBalanceProps) {\n super(props);\n }\n\n render() {\n // do not print equivalent if this element is the quote itself\n const quoteBlock = this.props.name != this.props.quote_symbol ? this.props.quote_symbol.concat(\" \").concat(this.props.quote_equivalent.toLocaleString()) : null\n\n // const accessory = SymbolAccessories.filter((accessory) => {\n // return accessory.name == this.props.name\n // })\n //\n // const icon = accessory.length > 0 ? accessory.pop().icon : null\n\n return (\n
\n
\n {/*{icon}*/}\n
\n
\n {this.props.name}\n
\n
\n
\n {Math.trunc(this.props.percentage)}%\n
\n
\n
\n
\n
\n
\n
\n
\n {this.props.amount.toFixed(5)} {this.props.name}\n
\n
\n
\n \n {quoteBlock}\n
\n
\n
\n
\n )\n }\n\n}\n\nexport type WalletCardProps = {\n quote: string,\n}\n\nexport class WalletCard extends Component\n <{}, { balances: Array }> {\n // constructor(props) {\n // super(props);\n // }\n\n state =\n {\n balances: []\n }\n\n totalQuoteBalance() {\n let total = 0\n\n this.state.balances.forEach((balance: Balance) => {\n if (balance.currency == balance.quote) {\n total += balance.amount\n } else {\n total += balance.quote_equivalent\n }\n })\n\n return total\n }\n\n renderCoinBalances() {\n return (\n this.state.balances.map((balance: Balance) => {\n const percentage_amount = balance.quote == balance.currency ? balance.amount : balance.quote_equivalent;\n\n return (\n \n )\n })\n )\n }\n\n componentDidMount() {\n socket.on(EventName.NewTick, (data: NewTickMessage) => {\n this.setState({\n balances: data.balances\n })\n })\n\n socket.on(EventName.FirstConnect, (data: FirstConnectMessage) => {\n this.setState({\n balances: data.balances\n })\n })\n }\n\n render() {\n return (\n
\n \n
\n
\n

Your Wallets

\n
\n \n \n
\n
\n
\n\n {this.renderCoinBalances()}\n\n
\n
\n Total Balance ≈ USD {this.totalQuoteBalance().toLocaleString()}\n
\n
\n
\n \n )\n }\n}","/home/giulio/dev/gkaching/websrc/src/components/Overlays.tsx",["26"],"import React, {Component} from \"react\";\nimport {socket} from \"../index\";\nimport {EventName} from \"../types\";\n\nexport type ModalProps = {\n show: boolean,\n positionId: number,\n toggleConfirmation: any\n}\n\nexport class ClosePositionModal extends Component {\n constructor(props: ModalProps) {\n super(props);\n }\n\n render() {\n if (!this.props.show) {\n return null\n }\n\n return (\n
\n
\n
\n
\n
\n\n {/*This element is to trick the browser into centering the modal contents. -->*/}\n \n\n {/*Modal panel, show/hide based on modal state.*/}\n\n {/*Entering: \"ease-out duration-300\"*/}\n {/* From: \"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95\"*/}\n {/* To: \"opacity-100 translate-y-0 sm:scale-100\"*/}\n {/*Leaving: \"ease-in duration-200\"*/}\n {/* From: \"opacity-100 translate-y-0 sm:scale-100\"*/}\n {/* To: \"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95\"*/}\n\n \n
\n
\n \n {/*Heroicon name: exclamation -->*/}\n \n \n \n
\n
\n

\n Close position\n

\n
\n

\n Are you sure you want to close the position? This action cannot be undone.\n

\n
\n
\n
\n
\n
\n \n \n
\n
\n
\n \n )\n }\n}","/home/giulio/dev/gkaching/websrc/src/index.tsx",[],{"ruleId":"27","severity":1,"message":"28","line":45,"column":5,"nodeType":"29","messageId":"30","endLine":47,"endColumn":6},{"ruleId":"27","severity":1,"message":"28","line":14,"column":5,"nodeType":"29","messageId":"30","endLine":16,"endColumn":6},{"ruleId":"31","severity":1,"message":"32","line":20,"column":44,"nodeType":"33","messageId":"34","endLine":20,"endColumn":46},{"ruleId":"31","severity":1,"message":"35","line":83,"column":34,"nodeType":"33","messageId":"34","endLine":83,"endColumn":36},{"ruleId":"31","severity":1,"message":"35","line":96,"column":57,"nodeType":"33","messageId":"34","endLine":96,"endColumn":59},{"ruleId":"27","severity":1,"message":"28","line":12,"column":5,"nodeType":"29","messageId":"30","endLine":14,"endColumn":6},"@typescript-eslint/no-useless-constructor","Useless constructor.","MethodDefinition","noUselessConstructor","eqeqeq","Expected '!==' and instead saw '!='.","BinaryExpression","unexpected","Expected '===' and instead saw '=='."] \ No newline at end of file diff --git a/websrc/.gitignore b/websrc/.gitignore index a6c7c28..4d29575 100644 --- a/websrc/.gitignore +++ b/websrc/.gitignore @@ -1 +1,23 @@ -*.js +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/websrc/README.md b/websrc/README.md new file mode 100644 index 0000000..02aac3f --- /dev/null +++ b/websrc/README.md @@ -0,0 +1,70 @@ +# Getting Started with Create React App + +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `yarn start` + +Runs the app in the development mode.\ +Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.\ +You will also see any lint errors in the console. + +### `yarn test` + +Launches the test runner in the interactive watch mode.\ +See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `yarn build` + +Builds the app for production to the `build` folder.\ +It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.\ +Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `yarn eject` + +**Note: this is a one-way operation. Once you `eject`, you can’t go back!** + +If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. + +You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). + +### Code Splitting + +This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) + +### Analyzing the Bundle Size + +This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) + +### Making a Progressive Web App + +This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) + +### Advanced Configuration + +This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) + +### Deployment + +This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) + +### `yarn build` fails to minify + +This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) diff --git a/websrc/components/Cards.tsx b/websrc/components/Cards.tsx index 5897448..9222871 100644 --- a/websrc/components/Cards.tsx +++ b/websrc/components/Cards.tsx @@ -28,7 +28,7 @@ class CoinBalance extends Component { return (
-
+
{/*{icon}*/}
diff --git a/websrc/components/Currency.tsx b/websrc/components/Currency.tsx deleted file mode 100644 index f2068a6..0000000 --- a/websrc/components/Currency.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import {Button, ButtonGroup, Dropdown} from "react-bootstrap"; -import React, {Component} from "react"; -import DropdownItem from "react-bootstrap/DropdownItem"; -import {CurrencyPair} from "../types"; - - -export type CurrencyPairProps = { - active_pair: CurrencyPair, - pairs: Array -} - -export class CurrencyDropdown extends Component { - constructor(props) { - super(props); - } - - dropdownItems() { - return this.props.pairs.map((pair) => { - return ( - {pair.base} / {pair.quote} ) - }) - } - - render() { - return ( - - - - {this.props.pairs.length > 0 && - - <> - - - - {this.dropdownItems()} - - - } - - - ) - } - -} - diff --git a/websrc/components/Events.tsx b/websrc/components/Events.tsx deleted file mode 100644 index 3547b19..0000000 --- a/websrc/components/Events.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React, {Component} from "react"; -import {Container, ListGroup} from "react-bootstrap"; - - -export type EventProp = { - id: number, - name: string, - tick: number -} - -export class Events extends Component<{ events: Array }> { - constructor(props) { - super(props); - } - - state = { - events: this.props.events - } - - mapEvents() { - return this.state.events.map((event: EventProp) => { - return ( - - {event.name} @ Tick {event.tick} - - ) - }) - } - - render() { - return ( - -
-

Events

-
- - {this.mapEvents()} - -
- ) - } -} \ No newline at end of file diff --git a/websrc/components/RToast.tsx b/websrc/components/RToast.tsx deleted file mode 100644 index a4036c7..0000000 --- a/websrc/components/RToast.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, {Component} from 'react'; -import {Toast} from 'react-bootstrap'; -import {socket} from "../"; - -type ToastProps = { - title: string, - content?: string, - bg?: string -} - -type ToastState = { - lastUpdated: Date, - show: boolean -} - - -export class RToast extends Component { - state = { - lastUpdated: new Date(), - show: false - } - - constructor(props: ToastProps) { - super(props) - } - - componentDidMount() { - socket.on("connect", () => { - this.setState({show: true}) - }) - } - - tick() { - this.setState({lastUpdated: new Date()}) - } - - render() { - return ( - - - {this.props.title} - {this.state.lastUpdated.toLocaleTimeString('en-GB')} - - {this.props.content ? {this.props.content} : null} - - ) - } -} \ No newline at end of file diff --git a/websrc/package.json b/websrc/package.json index 4642e1e..faa66e3 100644 --- a/websrc/package.json +++ b/websrc/package.json @@ -1,40 +1,55 @@ { - "name": "rustico", - "version": "1.0.0", - "main": "index.tsx", - "license": "MIT", - "devDependencies": { - "@types/luxon": "^1.25.0", + "name": "bfxbot", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/jest-dom": "^5.11.4", + "@testing-library/react": "^11.1.0", + "@testing-library/user-event": "^12.1.10", + "@types/jest": "^26.0.19", + "@types/node": "^14.14.16", "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", - "@types/react-helmet": "^6.1.0", - "@types/react-plotly.js": "^2.2.4", - "@types/socket.io-client": "^1.4.34", - "parcel-bundler": "^1.12.4", - "react-plotly.js": "^2.5.1", - "typescript": "^4.1.2" - }, - "dependencies": { - "@types/classnames": "^2.2.11", - "autoprefixer": "^10.1.0", - "classnames": "^2.2.6", - "css": "^3.0.0", - "luxon": "^1.25.0", - "plotly.js": "^1.58.2", - "postcss": "^8.2.1", - "postcss-cli": "^8.3.1", + "plotly.js": "^1.58.4", "react": "^17.0.1", - "react-cryptocoins": "^1.0.11", "react-dom": "^17.0.1", - "react-helmet": "^6.1.0", - "socket.io-client": "~3", - "tailwindcss": "^2.0.2" + "react-plotly.js": "^2.5.1", + "react-scripts": "4.0.1", + "socket.io-client": "^3.0.4", + "typescript": "^4.1.3", + "web-vitals": "^0.2.4" }, "scripts": { - "build:tailwind": "tailwindcss build css/index.css -o css/tailwind.css", - "watch": "parcel watch index.tsx -d ../static", - "prestart": "yarn run build:tailwind", - "prebuild": "yarn run build:tailwind", - "start": "python ../main.py & yarn run watch" + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@types/luxon": "^1.25.0", + "@types/react-helmet": "^6.1.0", + "@types/react-plotly.js": "^2.2.4", + "luxon": "^1.25.0", + "react-cryptocoins": "^1.0.11", + "react-helmet": "^6.1.0", + "react-plotly": "^1.0.0" } } diff --git a/websrc/public/favicon.ico b/websrc/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a GIT binary patch literal 3870 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ literal 0 HcmV?d00001 diff --git a/websrc/public/index.html b/websrc/public/index.html new file mode 100644 index 0000000..aa069f2 --- /dev/null +++ b/websrc/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/websrc/public/logo192.png b/websrc/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 GIT binary patch literal 5347 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 diff --git a/websrc/public/manifest.json b/websrc/public/manifest.json new file mode 100644 index 0000000..080d6c7 --- /dev/null +++ b/websrc/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/websrc/public/robots.txt b/websrc/public/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/websrc/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/websrc/src/components/App.tsx b/websrc/src/components/App.tsx new file mode 100644 index 0000000..2c49344 --- /dev/null +++ b/websrc/src/components/App.tsx @@ -0,0 +1,131 @@ +import React, { Component } from "react"; +import { + Balance, + CurrencyPair, + EventName, + EventProp, + FirstConnectMessage, + NewEventMessage, + NewTickMessage, + PositionProp +} from "../types"; +import { socket } from "../index"; +import { symbolToPair } from "../utils"; +import { Helmet } from "react-helmet"; +import { Navbar, Sidebar } from "./Navbars"; +import { Statusbar } from "./Statusbar"; +import { PositionsTable } from "./Tables"; +import RPlot from "./RPlot"; + +type AppState = { + current_price: number, + current_tick: number, + last_update: Date, + positions: Array, + events: Array, + active_pair: CurrencyPair, + available_pairs: Array, + balances: Array +} + +class App extends Component<{}, AppState> { + event_id = 0; + + state = { + current_price: 0, + current_tick: 0, + last_update: new Date(), + positions: [], + events: [], + balances: [], + active_pair: symbolToPair("tBTCUSD"), + available_pairs: [] + } + + constructor(props: {}) { + super(props) + } + + componentDidMount() { + socket.on(EventName.FirstConnect, (data: FirstConnectMessage) => { + this.setState({ + current_price: data.prices[data.prices.length - 1], + current_tick: data.ticks[data.ticks.length - 1], + last_update: new Date(), + positions: data.positions, + balances: data.balances + }) + }) + + socket.on(EventName.NewTick, (data: NewTickMessage) => { + this.setState({ + current_price: data.price, + current_tick: data.tick, + last_update: new Date(), + positions: data.positions, + balances: data.balances + }) + }) + + socket.on(EventName.NewEvent, (data: NewEventMessage) => { + // ignore new tick + if (!data.kind.toLowerCase().includes("new_tick")) { + const new_event: EventProp = { + id: this.event_id, + name: data.kind, + tick: data.tick + } + + this.event_id += 1 + + this.setState((state) => ({ + events: [...state.events, new_event] + })) + } + }) + } + + render() { + return ( + <> + + Rustico + - {String(this.state.current_price.toLocaleString())} {String(this.state.active_pair.base) + "/" + String(this.state.active_pair.quote)} + +
+
+ + +
+
+ +
+ +
+
+ +
+
+ + {this.state.positions.length > 0 ? + : null} + +
+ Made with ❤️ by the Peperone in a scantinato +
+
+ + +
+
+ + ) + } +} + +export default App; \ No newline at end of file diff --git a/websrc/src/components/Cards.tsx b/websrc/src/components/Cards.tsx new file mode 100644 index 0000000..54cfd35 --- /dev/null +++ b/websrc/src/components/Cards.tsx @@ -0,0 +1,154 @@ +import React, { Component } from 'react'; +import { Balance, EventName, FirstConnectMessage, NewTickMessage } from "../types"; +import { socket } from "../index"; + +export type CoinBalanceProps = { + name: string, + amount: number, + percentage: number, + quote_equivalent: number, + quote_symbol: string, +} + +class CoinBalance extends Component { + constructor(props: CoinBalanceProps) { + super(props); + } + + render() { + // do not print equivalent if this element is the quote itself + const quoteBlock = this.props.name != this.props.quote_symbol ? this.props.quote_symbol.concat(" ").concat(this.props.quote_equivalent.toLocaleString()) : null + + // const accessory = SymbolAccessories.filter((accessory) => { + // return accessory.name == this.props.name + // }) + // + // const icon = accessory.length > 0 ? accessory.pop().icon : null + + return ( +
this.changeProfitLossPeriod(1, PeriodUnit.MINUTE)}/> + onClick={() => this.changeProfitLossPeriod(1, PeriodUnit.MINUTE)} /> this.changeProfitLossPeriod(1, PeriodUnit.DAY)}/> + onClick={() => this.changeProfitLossPeriod(1, PeriodUnit.DAY)} /> this.changeProfitLossPeriod(1, PeriodUnit.WEEK)}/> + onClick={() => this.changeProfitLossPeriod(1, PeriodUnit.WEEK)} /> this.changeProfitLossPeriod(1, PeriodUnit.MONTH)}/> + onClick={() => this.changeProfitLossPeriod(1, PeriodUnit.MONTH)} /> this.changeProfitLossPeriod(1, PeriodUnit.YEAR)}/> + onClick={() => this.changeProfitLossPeriod(1, PeriodUnit.YEAR)} /> {/* this.changeProfitLossPeriod(1, PeriodUnit.MINUTE)}/>*/}
+
+ {/*{icon}*/} +
+
+ {this.props.name} +
+
+
+ {Math.trunc(this.props.percentage)}% +
+
+
+
+
+
+
+
+ {this.props.amount.toFixed(5)} {this.props.name} +
+
+
+
+ {quoteBlock} +
+
+
+
+ ) + } + +} + +export type WalletCardProps = { + quote: string, +} + +export class WalletCard extends Component + <{}, { balances: Array }> { + // constructor(props) { + // super(props); + // } + + state = + { + balances: [] + } + + totalQuoteBalance() { + let total = 0 + + this.state.balances.forEach((balance: Balance) => { + if (balance.currency == balance.quote) { + total += balance.amount + } else { + total += balance.quote_equivalent + } + }) + + return total + } + + renderCoinBalances() { + return ( + this.state.balances.map((balance: Balance) => { + const percentage_amount = balance.quote == balance.currency ? balance.amount : balance.quote_equivalent; + + return ( + + ) + }) + ) + } + + componentDidMount() { + socket.on(EventName.NewTick, (data: NewTickMessage) => { + this.setState({ + balances: data.balances + }) + }) + + socket.on(EventName.FirstConnect, (data: FirstConnectMessage) => { + this.setState({ + balances: data.balances + }) + }) + } + + render() { + return ( +
+
+
+
+

Your Wallets

+
+ + +
+
+
+ + {this.renderCoinBalances()} + +
+
+ Total Balance ≈ USD {this.totalQuoteBalance().toLocaleString()} +
+
+
+
+ ) + } +} \ No newline at end of file diff --git a/websrc/src/components/Currency.tsx b/websrc/src/components/Currency.tsx new file mode 100644 index 0000000..ef77363 --- /dev/null +++ b/websrc/src/components/Currency.tsx @@ -0,0 +1,9 @@ +import React, { Component } from "react"; +import { CurrencyPair } from "../types"; + + +export type CurrencyPairProps = { + active_pair: CurrencyPair, + pairs: Array +} + diff --git a/websrc/components/Icons.tsx b/websrc/src/components/Icons.tsx similarity index 97% rename from websrc/components/Icons.tsx rename to websrc/src/components/Icons.tsx index eaeba3c..f2cd7a2 100644 --- a/websrc/components/Icons.tsx +++ b/websrc/src/components/Icons.tsx @@ -9,7 +9,7 @@ class Icon extends Component { private readonly width: string; private readonly height: string; - constructor(props) { + constructor(props: IconProps) { super(props); this.width = "w-" + this.props.width.toString(); diff --git a/websrc/components/Navbars.tsx b/websrc/src/components/Navbars.tsx similarity index 71% rename from websrc/components/Navbars.tsx rename to websrc/src/components/Navbars.tsx index 98b8018..dccd635 100644 --- a/websrc/components/Navbars.tsx +++ b/websrc/src/components/Navbars.tsx @@ -1,8 +1,8 @@ -import React, {Component} from "react"; -import {WalletCard} from "./Cards"; +import React, { Component } from "react"; +import { WalletCard } from "./Cards"; -export class Navbar extends Component { - constructor(props) { +export class Navbar extends Component { + constructor(props: {}) { super(props); } @@ -27,9 +27,9 @@ export class Navbar extends Component {
  • + stroke="currentColor"> + d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" /> Reports @@ -39,11 +39,11 @@ export class Navbar extends Component {
  • + stroke="currentColor"> + d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /> + d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> Settings @@ -55,7 +55,7 @@ export class Navbar extends Component { } export class Sidebar extends Component { - constructor(props) { + constructor(props: {}) { super(props); } @@ -64,7 +64,7 @@ export class Sidebar extends Component { ) } diff --git a/websrc/components/Overlays.tsx b/websrc/src/components/Overlays.tsx similarity index 99% rename from websrc/components/Overlays.tsx rename to websrc/src/components/Overlays.tsx index 635db43..b6c7368 100644 --- a/websrc/components/Overlays.tsx +++ b/websrc/src/components/Overlays.tsx @@ -9,7 +9,7 @@ export type ModalProps = { } export class ClosePositionModal extends Component { - constructor(props) { + constructor(props: ModalProps) { super(props); } diff --git a/websrc/components/RPlot.tsx b/websrc/src/components/RPlot.tsx similarity index 66% rename from websrc/components/RPlot.tsx rename to websrc/src/components/RPlot.tsx index 5eba83a..08a61a8 100644 --- a/websrc/components/RPlot.tsx +++ b/websrc/src/components/RPlot.tsx @@ -1,8 +1,8 @@ -import React, {Component} from "react" +import React, { Component } from "react" import Plot from "react-plotly.js" -import {socket} from '../'; -import {EventName, NewTickMessage} from "../types"; +import { socket } from '../'; +import { EventName, NewTickMessage } from "../types"; type FirstConnectData = { @@ -29,11 +29,11 @@ class RPlot extends Component<{}, PlotState> { state = { x: [], y: [], - current_price_line: {x0: 0, x1: 0, y0: 0, y1: 0}, + current_price_line: { x0: 0, x1: 0, y0: 0, y1: 0 }, positions_price_lines: [] } - constructor(props) { + constructor(props: {}) { super(props) } @@ -79,24 +79,24 @@ class RPlot extends Component<{}, PlotState> { } render() { - let additional_shapes = [] + // let additional_shapes: ConcatArray<{ type: string; x0: number; y0: number; x1: number; y1: number; line: { color: string; width: number; dash: string; }; }> = [] - if (this.state.positions_price_lines.length > 0) { - additional_shapes = this.state.positions_price_lines.map((priceline: PriceLine) => { - return { - type: 'line', - x0: priceline.x0, - y0: priceline.y0, - x1: priceline.x1, - y1: priceline.y1, - line: { - color: 'rgb(1, 1, 1)', - width: 1, - dash: 'solid' - } - } - }) - } + // if (this.state.positions_price_lines.length > 0) { + // additional_shapes = this.state.positions_price_lines.map((priceline: PriceLine) => { + // return { + // type: 'line', + // x0: priceline.x0, + // y0: priceline.y0, + // x1: priceline.x1, + // y1: priceline.y1, + // line: { + // color: 'rgb(1, 1, 1)', + // width: 1, + // dash: 'solid' + // } + // } + // }) + // } return ( { pad: 4 }, dragmode: "pan", - shapes: [ - { - type: 'line', - x0: this.state.current_price_line.x0, - y0: this.state.current_price_line.y0, - x1: this.state.current_price_line.x1, - y1: this.state.current_price_line.y1, - line: { - color: 'rgb(50, 171, 96)', - width: 2, - dash: 'dashdot' - } - }, - ].concat(additional_shapes), + // shapes: [ + // { + // type: 'line', + // x0: this.state.current_price_line.x0, + // y0: this.state.current_price_line.y0, + // x1: this.state.current_price_line.x1, + // y1: this.state.current_price_line.y1, + // line: { + // color: 'rgb(50, 171, 96)', + // width: 2, + // dash: 'dashdot' + // } + // }, + // ].concat(additional_shapes), xaxis: { title: { text: 'Tick', @@ -158,7 +158,7 @@ class RPlot extends Component<{}, PlotState> { displayModeBar: false, responsive: true, }} - style={{width: '100%', height: '100%'}} + style={{ width: '100%', height: '100%' }} /> ) } diff --git a/websrc/components/Statusbar.tsx b/websrc/src/components/Statusbar.tsx similarity index 81% rename from websrc/components/Statusbar.tsx rename to websrc/src/components/Statusbar.tsx index ff48e09..a0808b1 100644 --- a/websrc/components/Statusbar.tsx +++ b/websrc/src/components/Statusbar.tsx @@ -1,7 +1,7 @@ -import React, {Component} from "react"; -import {EventName, GetProfitLossMessage, NewTickMessage, PutProfitLossMessage} from "../types"; -import {socket} from "../index"; -import {DateTime} from "luxon"; +import React, { Component } from "react"; +import { EventName, GetProfitLossMessage, NewTickMessage, PutProfitLossMessage } from "../types"; +import { socket } from "../index"; +import { DateTime } from "luxon"; type QuoteStatusProps = { percentage?: boolean, @@ -17,9 +17,14 @@ export class QuoteStatus extends Component { private sign: string; private signClass: string; - constructor(props) { + constructor(props: QuoteStatusProps) { super(props); + this.whole = 0; + this.decimal = 0; + this.sign = ""; + this.signClass = ""; + this.deriveProps() } @@ -45,8 +50,8 @@ export class QuoteStatus extends Component { if (this.props.percentage) { return ( <> - - {this.renderSign()} + + {this.renderSign()} {Math.abs(this.props.amount).toFixed(2)} % @@ -89,20 +94,26 @@ type DateButtonState = { class DateButton extends Component { private classSelected: string = "appearance-none py-4 text-blue-600 border-b border-blue-600 mr-3"; private classNotSelected: string = "appearance-none py-4 text-gray-600 border-b border-transparent hover:border-gray-800 mr-3"; - private currentClass: string; + private currentClass: string = ""; state = { - selected: this.props.selected_default + selected: false } - - constructor(props) { + + constructor(props: DateButtonProps) { super(props); + if (this.props.selected_default) { + this.state = { + selected: this.props.selected_default + } + } + this.updateClass() } onClick() { - this.setState({selected: !this.state.selected}, this.updateClass) + this.setState({ selected: !this.state.selected }, this.updateClass) this.props.onClick() } @@ -114,7 +125,7 @@ class DateButton extends Component { render() { return ( ) @@ -140,7 +151,7 @@ type StatusBarState = { export class Statusbar extends Component { - constructor(props) { + constructor(props: NewTickMessage) { super(props); this.state = { @@ -223,21 +234,21 @@ export class Statusbar extends Component {
  • @@ -260,8 +271,8 @@ export class Statusbar extends Component {
    - + viewBox="0 0 20 20"> +
    @@ -270,20 +281,20 @@ export class Statusbar extends Component {
    + subtitle={"Bitcoin price"} />
    + subtitle={"since last ".concat(this.state.pl_period_unit)} />
    + amount={this.state.pl_perc} + subtitle={"since last ".concat(this.state.pl_period_unit)} />
    diff --git a/websrc/components/Tables.tsx b/websrc/src/components/Tables.tsx similarity index 86% rename from websrc/components/Tables.tsx rename to websrc/src/components/Tables.tsx index e8a3a5d..b1df283 100644 --- a/websrc/components/Tables.tsx +++ b/websrc/src/components/Tables.tsx @@ -1,6 +1,6 @@ -import React, {Component} from "react" -import {PositionProp} from "../types"; -import {ClosePositionModal} from "./Overlays"; +import React, { Component } from "react" +import { PositionProp } from "../types"; +import { ClosePositionModal } from "./Overlays"; type PositionsTableState = { showConfirmation: boolean, @@ -8,7 +8,7 @@ type PositionsTableState = { } export class PositionsTable extends Component<{ positions: Array }, PositionsTableState> { - constructor(props) { + constructor(props: { positions: PositionProp[]; } | Readonly<{ positions: PositionProp[]; }>) { super(props); this.toggleConfirmation = this.toggleConfirmation.bind(this) } @@ -42,13 +42,13 @@ export class PositionsTable extends Component<{ positions: Array } renderTableHead() { return ["status", "currency pair", "base price", "amount", "Profit/Loss", "actions"].map((entry) => { - return ( - - {entry} - - ) - } + return ( + + {entry} + + ) + } ) } @@ -65,7 +65,7 @@ export class PositionsTable extends Component<{ positions: Array } - {position.state} + {position.state} @@ -115,20 +115,20 @@ export class PositionsTable extends Component<{ positions: Array } return (
    + show={this.state.showConfirmation} />
    - - {this.renderTableHead()} - + + {this.renderTableHead()} + - {this.renderTableRows()} + {this.renderTableRows()}
    diff --git a/websrc/css/.gitignore b/websrc/src/css/.gitignore similarity index 100% rename from websrc/css/.gitignore rename to websrc/src/css/.gitignore diff --git a/websrc/css/index.css b/websrc/src/css/index.css similarity index 100% rename from websrc/css/index.css rename to websrc/src/css/index.css diff --git a/websrc/src/custom.d.ts b/websrc/src/custom.d.ts new file mode 100644 index 0000000..631dc87 --- /dev/null +++ b/websrc/src/custom.d.ts @@ -0,0 +1 @@ +declare module 'react-cryptocoins'; \ No newline at end of file diff --git a/websrc/index.tsx b/websrc/src/index.tsx similarity index 90% rename from websrc/index.tsx rename to websrc/src/index.tsx index caa821b..56b8b2c 100644 --- a/websrc/index.tsx +++ b/websrc/src/index.tsx @@ -4,7 +4,7 @@ import "./css/tailwind.css"; import App from "./components/App"; import io from "socket.io-client"; -export const socket = io(); +export const socket = io.io(); socket.on("connect", function () { console.log("Connected!") diff --git a/websrc/src/react-app-env.d.ts b/websrc/src/react-app-env.d.ts new file mode 100644 index 0000000..6431bc5 --- /dev/null +++ b/websrc/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/websrc/types.ts b/websrc/src/types.ts similarity index 94% rename from websrc/types.ts rename to websrc/src/types.ts index 50c56e8..7b00468 100644 --- a/websrc/types.ts +++ b/websrc/src/types.ts @@ -1,4 +1,3 @@ -import {EventProp} from "./components/Events"; /******************************* * Types @@ -18,6 +17,12 @@ export type CurrencyPair = { quote: string } +export type EventProp = { + id: number, + name: string, + tick: number +} + export type FirstConnectMessage = { ticks: Array, prices: Array, diff --git a/websrc/utils.ts b/websrc/src/utils.ts similarity index 79% rename from websrc/utils.ts rename to websrc/src/utils.ts index 99fc46d..bd97ce9 100644 --- a/websrc/utils.ts +++ b/websrc/src/utils.ts @@ -1,12 +1,16 @@ -import {CurrencyPair} from "./types"; +import { CurrencyPair } from "./types"; // import * as Icon from 'react-cryptocoins' -import {Btc, Eth, Xmr} from 'react-cryptocoins'; +import { Btc, Eth, Xmr } from 'react-cryptocoins'; export function symbolToPair(symbol: string): CurrencyPair { const symbol_regex = "t(?[a-zA-Z]{3})(?[a-zA-Z]{3})" const match = symbol.match(symbol_regex) + if (!match?.groups) { + return { base: "undefined", quote: "undefined" } + } + return { base: match.groups.base, quote: match.groups.quote diff --git a/websrc/tsconfig.json b/websrc/tsconfig.json index d67ae04..1cac4a1 100644 --- a/websrc/tsconfig.json +++ b/websrc/tsconfig.json @@ -1,12 +1,29 @@ { - "compilerOptions": { - "lib": ["esnext", "DOM"], - "jsx": "react", - "moduleResolution": "node", - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - }, - "exclude": [ - "node_modules" + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" ], -} \ No newline at end of file + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": [ + "src" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/websrc/yarn.lock b/websrc/yarn.lock index a40aceb..10311c3 100644 --- a/websrc/yarn.lock +++ b/websrc/yarn.lock @@ -11,54 +11,77 @@ orbit-camera-controller "^4.0.0" turntable-camera-controller "^3.0.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": +"@babel/code-frame@7.10.4", "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.5.5": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== dependencies: "@babel/highlight" "^7.10.4" -"@babel/compat-data@^7.12.5", "@babel/compat-data@^7.12.7": +"@babel/compat-data@^7.12.1", "@babel/compat-data@^7.12.5", "@babel/compat-data@^7.12.7": version "7.12.7" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.12.7.tgz#9329b4782a7d6bbd7eef57e11addf91ee3ef1e41" integrity sha512-YaxPMGs/XIWtYqrdEOZOCPsVWfEoriXopnsz3/i7apYPXQ3698UFhS6dVT1KN5qOsWmVgw/FOrmQgpRaZayGsw== -"@babel/core@^7.4.4": - version "7.12.10" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.10.tgz#b79a2e1b9f70ed3d84bbfb6d8c4ef825f606bccd" - integrity sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w== +"@babel/core@7.12.3": + version "7.12.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.3.tgz#1b436884e1e3bff6fb1328dc02b208759de92ad8" + integrity sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g== dependencies: "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.12.10" + "@babel/generator" "^7.12.1" "@babel/helper-module-transforms" "^7.12.1" - "@babel/helpers" "^7.12.5" - "@babel/parser" "^7.12.10" - "@babel/template" "^7.12.7" - "@babel/traverse" "^7.12.10" - "@babel/types" "^7.12.10" + "@babel/helpers" "^7.12.1" + "@babel/parser" "^7.12.3" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.12.1" + "@babel/types" "^7.12.1" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.1" json5 "^2.1.2" lodash "^4.17.19" + resolve "^1.3.2" semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.12.10", "@babel/generator@^7.4.4": - version "7.12.10" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.10.tgz#2b188fc329fb8e4f762181703beffc0fe6df3460" - integrity sha512-6mCdfhWgmqLdtTkhXjnIz0LcdVCd26wS2JXRtj2XY0u5klDsXBREA/pG5NVOuVnF2LUrBGNFtQkIqqTbblg0ww== +"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.5", "@babel/core@^7.8.4", "@babel/core@^7.9.0": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.7.tgz#bf55363c08c8352a37691f7216ec30090bf7e3bf" + integrity sha512-tRKx9B53kJe8NCGGIxEQb2Bkr0riUIEuN7Sc1fxhs5H8lKlCWUvQCSNMVIB0Meva7hcbCRJ76de15KoLltdoqw== dependencies: - "@babel/types" "^7.12.10" + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.12.5" + "@babel/helper-module-transforms" "^7.12.1" + "@babel/helpers" "^7.12.5" + "@babel/parser" "^7.12.7" + "@babel/template" "^7.12.7" + "@babel/traverse" "^7.12.7" + "@babel/types" "^7.12.7" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.19" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.12.1", "@babel/generator@^7.12.5": + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.5.tgz#a2c50de5c8b6d708ab95be5e6053936c1884a4de" + integrity sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A== + dependencies: + "@babel/types" "^7.12.5" jsesc "^2.5.1" source-map "^0.5.0" -"@babel/helper-annotate-as-pure@^7.10.4", "@babel/helper-annotate-as-pure@^7.12.10": - version "7.12.10" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz#54ab9b000e60a93644ce17b3f37d313aaf1d115d" - integrity sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ== +"@babel/helper-annotate-as-pure@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3" + integrity sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA== dependencies: - "@babel/types" "^7.12.10" + "@babel/types" "^7.10.4" "@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4": version "7.10.4" @@ -68,14 +91,14 @@ "@babel/helper-explode-assignable-expression" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/helper-builder-react-jsx-experimental@^7.12.10": - version "7.12.10" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.12.10.tgz#a58cb96a793dc0fcd5c9ed3bb36d62fdc60534c2" - integrity sha512-3Kcr2LGpL7CTRDTTYm1bzeor9qZbxbvU2AxsLA6mUG9gYarSfIKMK0UlU+azLWI+s0+BH768bwyaziWB2NOJlQ== +"@babel/helper-builder-react-jsx-experimental@^7.12.4": + version "7.12.4" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.12.4.tgz#55fc1ead5242caa0ca2875dcb8eed6d311e50f48" + integrity sha512-AjEa0jrQqNk7eDQOo0pTfUOwQBMF+xVqrausQwT9/rTKy0g04ggFNaJpaE09IQMn9yExluigWMJcj0WC7bq+Og== dependencies: - "@babel/helper-annotate-as-pure" "^7.12.10" - "@babel/helper-module-imports" "^7.12.5" - "@babel/types" "^7.12.10" + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-module-imports" "^7.12.1" + "@babel/types" "^7.12.1" "@babel/helper-builder-react-jsx@^7.10.4": version "7.10.4" @@ -85,7 +108,7 @@ "@babel/helper-annotate-as-pure" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/helper-compilation-targets@^7.12.5": +"@babel/helper-compilation-targets@^7.12.1", "@babel/helper-compilation-targets@^7.12.5": version "7.12.5" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.5.tgz#cb470c76198db6a24e9dbc8987275631e5d29831" integrity sha512-+qH6NrscMolUlzOYngSBMIOQpKUGPPsc61Bu5W10mg84LxZ7cmvnBHzARKbDoFxVvqqAbj6Tg6N7bSrWSPXMyw== @@ -140,11 +163,11 @@ "@babel/types" "^7.10.4" "@babel/helper-get-function-arity@^7.10.4": - version "7.12.10" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz#b158817a3165b5faa2047825dfa61970ddcc16cf" - integrity sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag== + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" + integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== dependencies: - "@babel/types" "^7.12.10" + "@babel/types" "^7.10.4" "@babel/helper-hoist-variables@^7.10.4": version "7.10.4" @@ -160,7 +183,7 @@ dependencies: "@babel/types" "^7.12.7" -"@babel/helper-module-imports@^7.12.1", "@babel/helper-module-imports@^7.12.5": +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.1", "@babel/helper-module-imports@^7.12.5": version "7.12.5" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz#1bfc0229f794988f76ed0a4d4e90860850b54dfb" integrity sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA== @@ -183,11 +206,11 @@ lodash "^4.17.19" "@babel/helper-optimise-call-expression@^7.10.4": - version "7.12.10" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz#94ca4e306ee11a7dd6e9f42823e2ac6b49881e2d" - integrity sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ== + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.7.tgz#7f94ae5e08721a49467346aa04fd22f750033b9c" + integrity sha512-I5xc9oSJ2h59OwyUqjv95HRyzxj53DAubUERgQMrpcCEYQyToeHA+NEcUEsVWB4j53RDeskeBJ0SgRAYHDBckw== dependencies: - "@babel/types" "^7.12.10" + "@babel/types" "^7.12.7" "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.10.4" @@ -254,7 +277,7 @@ "@babel/traverse" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/helpers@^7.12.5": +"@babel/helpers@^7.12.1", "@babel/helpers@^7.12.5": version "7.12.5" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.12.5.tgz#1a1ba4a768d9b58310eda516c449913fe647116e" integrity sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA== @@ -272,10 +295,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.12.10", "@babel/parser@^7.12.7", "@babel/parser@^7.4.4": - version "7.12.10" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.10.tgz#824600d59e96aea26a5a2af5a9d812af05c3ae81" - integrity sha512-PJdRPwyoOqFAWfLytxrWwGrAxghCgh/yTNCYciOz8QgjflA7aZhECPZAa2VUedKg2+QMWkI0L9lynh2SNmNEgA== +"@babel/parser@^7.1.0", "@babel/parser@^7.12.3", "@babel/parser@^7.12.7", "@babel/parser@^7.7.0": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.7.tgz#fee7b39fe809d0e73e5b25eecaf5780ef3d73056" + integrity sha512-oWR02Ubp4xTLCAqPRiNIuMVgNO5Aif/xpXtabhzW2HWUD47XJsAB4Zd/Rg30+XeQA3juXigV7hlquOTmwqLiwg== "@babel/plugin-proposal-async-generator-functions@^7.12.1": version "7.12.1" @@ -286,7 +309,7 @@ "@babel/helper-remap-async-to-generator" "^7.12.1" "@babel/plugin-syntax-async-generators" "^7.8.0" -"@babel/plugin-proposal-class-properties@^7.12.1": +"@babel/plugin-proposal-class-properties@7.12.1", "@babel/plugin-proposal-class-properties@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz#a082ff541f2a29a4821065b8add9346c0c16e5de" integrity sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w== @@ -294,6 +317,15 @@ "@babel/helper-create-class-features-plugin" "^7.12.1" "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-proposal-decorators@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.12.1.tgz#59271439fed4145456c41067450543aee332d15f" + integrity sha512-knNIuusychgYN8fGJHONL0RbFxLGawhXOJNLBk75TniTsZZeA+wdkDuv6wp4lGwzQEKjZi6/WYtnb3udNPmQmQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.12.1" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-decorators" "^7.12.1" + "@babel/plugin-proposal-dynamic-import@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz#43eb5c2a3487ecd98c5c8ea8b5fdb69a2749b2dc" @@ -326,7 +358,7 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-proposal-nullish-coalescing-operator@^7.12.1": +"@babel/plugin-proposal-nullish-coalescing-operator@7.12.1", "@babel/plugin-proposal-nullish-coalescing-operator@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz#3ed4fff31c015e7f3f1467f190dbe545cd7b046c" integrity sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg== @@ -334,7 +366,15 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" -"@babel/plugin-proposal-numeric-separator@^7.12.7": +"@babel/plugin-proposal-numeric-separator@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.1.tgz#0e2c6774c4ce48be412119b4d693ac777f7685a6" + integrity sha512-MR7Ok+Af3OhNTCxYVjJZHS0t97ydnJZt/DbR4WISO39iDnhiD8XHrY12xuSJ90FFEGjir0Fzyyn7g/zY6hxbxA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-proposal-numeric-separator@^7.12.1", "@babel/plugin-proposal-numeric-separator@^7.12.7": version "7.12.7" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.7.tgz#8bf253de8139099fea193b297d23a9d406ef056b" integrity sha512-8c+uy0qmnRTeukiGsjLGy6uVs/TFjJchGXUeBqlG4VWYOdJWkhhVPdQ3uHwbmalfJwv2JsV0qffXP4asRfL2SQ== @@ -359,7 +399,16 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" -"@babel/plugin-proposal-optional-chaining@^7.12.7": +"@babel/plugin-proposal-optional-chaining@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.1.tgz#cce122203fc8a32794296fc377c6dedaf4363797" + integrity sha512-c2uRpY6WzaVDzynVY9liyykS+kVU+WRZPMPYpkelXH8KBt1oXoI89kPbZKKG/jDT5UK92FTW2fZkZaJhdiBabw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + +"@babel/plugin-proposal-optional-chaining@^7.12.1", "@babel/plugin-proposal-optional-chaining@^7.12.7": version "7.12.7" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.7.tgz#e02f0ea1b5dc59d401ec16fb824679f683d3303c" integrity sha512-4ovylXZ0PWmwoOvhU2vhnzVNnm88/Sm9nx7V8BPgMvAzn5zDou3/Awy0EjglyubVHasJj+XCEkr/r1X3P5elCA== @@ -384,20 +433,34 @@ "@babel/helper-create-regexp-features-plugin" "^7.12.1" "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-async-generators@^7.8.0": +"@babel/plugin-syntax-async-generators@^7.8.0", "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-class-properties@^7.12.1": +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.1", "@babel/plugin-syntax-class-properties@^7.8.3": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz#bcb297c5366e79bebadef509549cd93b04f19978" integrity sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA== dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-syntax-decorators@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.12.1.tgz#81a8b535b284476c41be6de06853a8802b98c5dd" + integrity sha512-ir9YW5daRrTYiy9UJ2TzdNIJEZu8KclVzDcfSt4iEmOtwQ4llPtWInNKJyKnVXp1vE4bbVd5S31M/im3mYMO1w== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-dynamic-import@^7.8.0": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" @@ -419,7 +482,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-json-strings@^7.8.0": +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.0", "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== @@ -433,55 +503,62 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0", "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-numeric-separator@^7.10.4": +"@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-object-rest-spread@^7.8.0": +"@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-optional-catch-binding@^7.8.0": +"@babel/plugin-syntax-optional-catch-binding@^7.8.0", "@babel/plugin-syntax-optional-catch-binding@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-optional-chaining@^7.8.0": +"@babel/plugin-syntax-optional-chaining@^7.8.0", "@babel/plugin-syntax-optional-chaining@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-top-level-await@^7.12.1": +"@babel/plugin-syntax-top-level-await@^7.12.1", "@babel/plugin-syntax-top-level-await@^7.8.3": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz#dd6c0b357ac1bb142d98537450a319625d13d2a0" integrity sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A== dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-syntax-typescript@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.1.tgz#460ba9d77077653803c3dd2e673f76d66b4029e5" + integrity sha512-UZNEcCY+4Dp9yYRCAHrHDU+9ZXLYaY9MgBXSRLkB9WjYFRR6quJBumfVrEkUxrePPBwFcpWfNKXqVRQQtm7mMA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-arrow-functions@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz#8083ffc86ac8e777fbe24b5967c4b2521f3cb2b3" @@ -563,10 +640,10 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.4" "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-flow-strip-types@^7.4.4": - version "7.12.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.12.10.tgz#d85e30ecfa68093825773b7b857e5085bbd32c95" - integrity sha512-0ti12wLTLeUIzu9U7kjqIn4MyOL7+Wibc7avsHhj4o1l5C0ATs8p2IMHrVYjm9t9wzhfEO6S3kxax0Rpdo8LTg== +"@babel/plugin-transform-flow-strip-types@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.12.1.tgz#8430decfa7eb2aea5414ed4a3fa6e1652b7d77c4" + integrity sha512-8hAtkmsQb36yMmEtk2JZ9JnVyDSnDOdlB+0nEGzIDLuK4yR3JcEjfuFPYkdEPSh8Id+rAMeBEn+X0iVEyho6Hg== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-flow" "^7.12.1" @@ -609,7 +686,7 @@ "@babel/helper-plugin-utils" "^7.10.4" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@^7.12.1", "@babel/plugin-transform-modules-commonjs@^7.4.4": +"@babel/plugin-transform-modules-commonjs@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz#fa403124542636c786cf9b460a0ffbb48a86e648" integrity sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag== @@ -674,16 +751,61 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-react-jsx@^7.0.0": - version "7.12.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.10.tgz#a7af3097c73479123594c8c8fe39545abebd44e3" - integrity sha512-MM7/BC8QdHXM7Qc1wdnuk73R4gbuOpfrSUgfV/nODGc86sPY1tgmY2M9E9uAnf2e4DOIp8aKGWqgZfQxnTNGuw== +"@babel/plugin-transform-react-constant-elements@^7.9.0": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.12.1.tgz#4471f0851feec3231cc9aaa0dccde39947c1ac1e" + integrity sha512-KOHd0tIRLoER+J+8f9DblZDa1fLGPwaaN1DI1TVHuQFOpjHV22C3CUB3obeC4fexHY9nx+fH0hQNvLFFfA1mxA== dependencies: - "@babel/helper-builder-react-jsx" "^7.10.4" - "@babel/helper-builder-react-jsx-experimental" "^7.12.10" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-react-display-name@7.12.1", "@babel/plugin-transform-react-display-name@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.1.tgz#1cbcd0c3b1d6648c55374a22fc9b6b7e5341c00d" + integrity sha512-cAzB+UzBIrekfYxyLlFqf/OagTvHLcVBb5vpouzkYkBclRPraiygVnafvAoipErZLI8ANv8Ecn6E/m5qPXD26w== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-react-jsx-development@^7.12.1", "@babel/plugin-transform-react-jsx-development@^7.12.7": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.7.tgz#4c2a647de79c7e2b16bfe4540677ba3121e82a08" + integrity sha512-Rs3ETtMtR3VLXFeYRChle5SsP/P9Jp/6dsewBQfokDSzKJThlsuFcnzLTDRALiUmTC48ej19YD9uN1mupEeEDg== + dependencies: + "@babel/helper-builder-react-jsx-experimental" "^7.12.4" "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-jsx" "^7.12.1" +"@babel/plugin-transform-react-jsx-self@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.12.1.tgz#ef43cbca2a14f1bd17807dbe4376ff89d714cf28" + integrity sha512-FbpL0ieNWiiBB5tCldX17EtXgmzeEZjFrix72rQYeq9X6nUK38HCaxexzVQrZWXanxKJPKVVIU37gFjEQYkPkA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-react-jsx-source@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.12.1.tgz#d07de6863f468da0809edcf79a1aa8ce2a82a26b" + integrity sha512-keQ5kBfjJNRc6zZN1/nVHCd6LLIHq4aUKcVnvE/2l+ZZROSbqoiGFRtT5t3Is89XJxBQaP7NLZX2jgGHdZvvFQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-react-jsx@^7.12.1", "@babel/plugin-transform-react-jsx@^7.12.7": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.7.tgz#8b14d45f6eccd41b7f924bcb65c021e9f0a06f7f" + integrity sha512-YFlTi6MEsclFAPIDNZYiCRbneg1MFGao9pPG9uD5htwE0vDbPaMUMeYd6itWjw7K4kro4UbdQf3ljmFl9y48dQ== + dependencies: + "@babel/helper-builder-react-jsx" "^7.10.4" + "@babel/helper-builder-react-jsx-experimental" "^7.12.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-jsx" "^7.12.1" + +"@babel/plugin-transform-react-pure-annotations@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.12.1.tgz#05d46f0ab4d1339ac59adf20a1462c91b37a1a42" + integrity sha512-RqeaHiwZtphSIUZ5I85PEH19LOSzxfuEazoY7/pWASCAIBuATQzpSVD+eT6MebeeZT2F4eSL0u4vw6n4Nm0Mjg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-regenerator@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz#5f0a28d842f6462281f06a964e88ba8d7ab49753" @@ -698,6 +820,16 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-transform-runtime@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.1.tgz#04b792057eb460389ff6a4198e377614ea1e7ba5" + integrity sha512-Ac/H6G9FEIkS2tXsZjL4RAdS3L3WHxci0usAnz7laPWUmFiGtj7tIASChqKZMHTSQTQY6xDbOq+V1/vIq3QrWg== + dependencies: + "@babel/helper-module-imports" "^7.12.1" + "@babel/helper-plugin-utils" "^7.10.4" + resolve "^1.8.1" + semver "^5.5.1" + "@babel/plugin-transform-shorthand-properties@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz#0bf9cac5550fce0cfdf043420f661d645fdc75e3" @@ -713,7 +845,7 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" -"@babel/plugin-transform-sticky-regex@^7.12.7": +"@babel/plugin-transform-sticky-regex@^7.12.1", "@babel/plugin-transform-sticky-regex@^7.12.7": version "7.12.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.7.tgz#560224613ab23987453948ed21d0b0b193fa7fad" integrity sha512-VEiqZL5N/QvDbdjfYQBhruN0HYjSPjC4XkeqW4ny/jNtH9gcbgaqBIXYEZCNnESMAGs0/K/R7oFGMhOyu/eIxg== @@ -727,13 +859,22 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-typeof-symbol@^7.12.10": - version "7.12.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.10.tgz#de01c4c8f96580bd00f183072b0d0ecdcf0dec4b" - integrity sha512-JQ6H8Rnsogh//ijxspCjc21YPd3VLVoYtAwv3zQmqAt8YGYUtdo5usNhdl4b9/Vir2kPFZl6n1h0PfUz4hJhaA== +"@babel/plugin-transform-typeof-symbol@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.1.tgz#9ca6be343d42512fbc2e68236a82ae64bc7af78a" + integrity sha512-EPGgpGy+O5Kg5pJFNDKuxt9RdmTgj5sgrus2XVeMp/ZIbOESadgILUbm50SNpghOh3/6yrbsH+NB5+WJTmsA7Q== dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-transform-typescript@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.12.1.tgz#d92cc0af504d510e26a754a7dbc2e5c8cd9c7ab4" + integrity sha512-VrsBByqAIntM+EYMqSm59SiMEf7qkmI9dqMt6RbD/wlwueWmYcI0FFK5Fj47pP6DRZm+3teXjosKlwcZJ5lIMw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.12.1" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-typescript" "^7.12.1" + "@babel/plugin-transform-unicode-escapes@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz#5232b9f81ccb07070b7c3c36c67a1b78f1845709" @@ -749,10 +890,82 @@ "@babel/helper-create-regexp-features-plugin" "^7.12.1" "@babel/helper-plugin-utils" "^7.10.4" -"@babel/preset-env@^7.4.4": - version "7.12.10" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.12.10.tgz#ca981b95f641f2610531bd71948656306905e6ab" - integrity sha512-Gz9hnBT/tGeTE2DBNDkD7BiWRELZt+8lSysHuDwmYXUIvtwZl0zI+D6mZgXZX0u8YBlLS4tmai9ONNY9tjRgRA== +"@babel/preset-env@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.12.1.tgz#9c7e5ca82a19efc865384bb4989148d2ee5d7ac2" + integrity sha512-H8kxXmtPaAGT7TyBvSSkoSTUK6RHh61So05SyEbpmr0MCZrsNYn7mGMzzeYoOUCdHzww61k8XBft2TaES+xPLg== + dependencies: + "@babel/compat-data" "^7.12.1" + "@babel/helper-compilation-targets" "^7.12.1" + "@babel/helper-module-imports" "^7.12.1" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-validator-option" "^7.12.1" + "@babel/plugin-proposal-async-generator-functions" "^7.12.1" + "@babel/plugin-proposal-class-properties" "^7.12.1" + "@babel/plugin-proposal-dynamic-import" "^7.12.1" + "@babel/plugin-proposal-export-namespace-from" "^7.12.1" + "@babel/plugin-proposal-json-strings" "^7.12.1" + "@babel/plugin-proposal-logical-assignment-operators" "^7.12.1" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.1" + "@babel/plugin-proposal-numeric-separator" "^7.12.1" + "@babel/plugin-proposal-object-rest-spread" "^7.12.1" + "@babel/plugin-proposal-optional-catch-binding" "^7.12.1" + "@babel/plugin-proposal-optional-chaining" "^7.12.1" + "@babel/plugin-proposal-private-methods" "^7.12.1" + "@babel/plugin-proposal-unicode-property-regex" "^7.12.1" + "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-class-properties" "^7.12.1" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/plugin-syntax-top-level-await" "^7.12.1" + "@babel/plugin-transform-arrow-functions" "^7.12.1" + "@babel/plugin-transform-async-to-generator" "^7.12.1" + "@babel/plugin-transform-block-scoped-functions" "^7.12.1" + "@babel/plugin-transform-block-scoping" "^7.12.1" + "@babel/plugin-transform-classes" "^7.12.1" + "@babel/plugin-transform-computed-properties" "^7.12.1" + "@babel/plugin-transform-destructuring" "^7.12.1" + "@babel/plugin-transform-dotall-regex" "^7.12.1" + "@babel/plugin-transform-duplicate-keys" "^7.12.1" + "@babel/plugin-transform-exponentiation-operator" "^7.12.1" + "@babel/plugin-transform-for-of" "^7.12.1" + "@babel/plugin-transform-function-name" "^7.12.1" + "@babel/plugin-transform-literals" "^7.12.1" + "@babel/plugin-transform-member-expression-literals" "^7.12.1" + "@babel/plugin-transform-modules-amd" "^7.12.1" + "@babel/plugin-transform-modules-commonjs" "^7.12.1" + "@babel/plugin-transform-modules-systemjs" "^7.12.1" + "@babel/plugin-transform-modules-umd" "^7.12.1" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.12.1" + "@babel/plugin-transform-new-target" "^7.12.1" + "@babel/plugin-transform-object-super" "^7.12.1" + "@babel/plugin-transform-parameters" "^7.12.1" + "@babel/plugin-transform-property-literals" "^7.12.1" + "@babel/plugin-transform-regenerator" "^7.12.1" + "@babel/plugin-transform-reserved-words" "^7.12.1" + "@babel/plugin-transform-shorthand-properties" "^7.12.1" + "@babel/plugin-transform-spread" "^7.12.1" + "@babel/plugin-transform-sticky-regex" "^7.12.1" + "@babel/plugin-transform-template-literals" "^7.12.1" + "@babel/plugin-transform-typeof-symbol" "^7.12.1" + "@babel/plugin-transform-unicode-escapes" "^7.12.1" + "@babel/plugin-transform-unicode-regex" "^7.12.1" + "@babel/preset-modules" "^0.1.3" + "@babel/types" "^7.12.1" + core-js-compat "^3.6.2" + semver "^5.5.0" + +"@babel/preset-env@^7.8.4", "@babel/preset-env@^7.9.5": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.12.7.tgz#54ea21dbe92caf6f10cb1a0a576adc4ebf094b55" + integrity sha512-OnNdfAr1FUQg7ksb7bmbKoby4qFOHw6DKWWUNB9KqnnCldxhxJlP+21dpyaWFmf2h0rTbOkXJtAGevY3XW1eew== dependencies: "@babel/compat-data" "^7.12.7" "@babel/helper-compilation-targets" "^7.12.5" @@ -813,12 +1026,12 @@ "@babel/plugin-transform-spread" "^7.12.1" "@babel/plugin-transform-sticky-regex" "^7.12.7" "@babel/plugin-transform-template-literals" "^7.12.1" - "@babel/plugin-transform-typeof-symbol" "^7.12.10" + "@babel/plugin-transform-typeof-symbol" "^7.12.1" "@babel/plugin-transform-unicode-escapes" "^7.12.1" "@babel/plugin-transform-unicode-regex" "^7.12.1" "@babel/preset-modules" "^0.1.3" - "@babel/types" "^7.12.10" - core-js-compat "^3.8.0" + "@babel/types" "^7.12.7" + core-js-compat "^3.7.0" semver "^5.5.0" "@babel/preset-modules@^0.1.3": @@ -832,14 +1045,63 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/runtime@^7.4.4", "@babel/runtime@^7.8.4": +"@babel/preset-react@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.12.1.tgz#7f022b13f55b6dd82f00f16d1c599ae62985358c" + integrity sha512-euCExymHCi0qB9u5fKw7rvlw7AZSjw/NaB9h7EkdTt5+yHRrXdiRTh7fkG3uBPpJg82CqLfp1LHLqWGSCrab+g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-react-display-name" "^7.12.1" + "@babel/plugin-transform-react-jsx" "^7.12.1" + "@babel/plugin-transform-react-jsx-development" "^7.12.1" + "@babel/plugin-transform-react-jsx-self" "^7.12.1" + "@babel/plugin-transform-react-jsx-source" "^7.12.1" + "@babel/plugin-transform-react-pure-annotations" "^7.12.1" + +"@babel/preset-react@^7.9.4": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.12.7.tgz#36d61d83223b07b6ac4ec55cf016abb0f70be83b" + integrity sha512-wKeTdnGUP5AEYCYQIMeXMMwU7j+2opxrG0WzuZfxuuW9nhKvvALBjl67653CWamZJVefuJGI219G591RSldrqQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-react-display-name" "^7.12.1" + "@babel/plugin-transform-react-jsx" "^7.12.7" + "@babel/plugin-transform-react-jsx-development" "^7.12.7" + "@babel/plugin-transform-react-jsx-self" "^7.12.1" + "@babel/plugin-transform-react-jsx-source" "^7.12.1" + "@babel/plugin-transform-react-pure-annotations" "^7.12.1" + +"@babel/preset-typescript@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.12.1.tgz#86480b483bb97f75036e8864fe404cc782cc311b" + integrity sha512-hNK/DhmoJPsksdHuI/RVrcEws7GN5eamhi28JkO52MqIxU8Z0QpmiSOQxZHWOHV7I3P4UjHV97ay4TcamMA6Kw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-typescript" "^7.12.1" + +"@babel/runtime-corejs3@^7.10.2": + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.12.5.tgz#ffee91da0eb4c6dae080774e94ba606368e414f4" + integrity sha512-roGr54CsTmNPPzZoCP1AmDXuBoNao7tnSA83TXTwt+UK5QVyh1DIJnrgYRPWKCF2flqZQXwa7Yr8v7VmLzF0YQ== + dependencies: + core-js-pure "^3.0.0" + regenerator-runtime "^0.13.4" + +"@babel/runtime@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.1.tgz#b4116a6b6711d010b2dad3b7b6e43bf1b9954740" + integrity sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.12.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.10.4", "@babel/template@^7.12.7", "@babel/template@^7.4.4": +"@babel/template@^7.10.4", "@babel/template@^7.12.7", "@babel/template@^7.3.3": version "7.12.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.7.tgz#c817233696018e39fbb6c491d2fb684e05ed43bc" integrity sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow== @@ -848,30 +1110,35 @@ "@babel/parser" "^7.12.7" "@babel/types" "^7.12.7" -"@babel/traverse@^7.10.4", "@babel/traverse@^7.12.1", "@babel/traverse@^7.12.10", "@babel/traverse@^7.12.5", "@babel/traverse@^7.4.4": - version "7.12.10" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.10.tgz#2d1f4041e8bf42ea099e5b2dc48d6a594c00017a" - integrity sha512-6aEtf0IeRgbYWzta29lePeYSk+YAFIC3kyqESeft8o5CkFlYIMX+EQDDWEiAQ9LHOA3d0oHdgrSsID/CKqXJlg== +"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.12.1", "@babel/traverse@^7.12.5", "@babel/traverse@^7.12.7", "@babel/traverse@^7.7.0": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.7.tgz#572a722408681cef17d6b0bef69ef2e728ca69f1" + integrity sha512-nMWaqsQEeSvMNypswUDzjqQ+0rR6pqCtoQpsqGJC4/Khm9cISwPTSpai57F6/jDaOoEGz8yE/WxcO3PV6tKSmQ== dependencies: "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.12.10" + "@babel/generator" "^7.12.5" "@babel/helper-function-name" "^7.10.4" "@babel/helper-split-export-declaration" "^7.11.0" - "@babel/parser" "^7.12.10" - "@babel/types" "^7.12.10" + "@babel/parser" "^7.12.7" + "@babel/types" "^7.12.7" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.19" -"@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.12.1", "@babel/types@^7.12.10", "@babel/types@^7.12.5", "@babel/types@^7.12.7", "@babel/types@^7.4.4": - version "7.12.10" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.10.tgz#7965e4a7260b26f09c56bcfcb0498af1f6d9b260" - integrity sha512-sf6wboJV5mGyip2hIpDSKsr80RszPinEFjsHTalMxZAZkoQ2/2yQzxlcFN52SJqsyPfLtPmenL4g2KB3KJXPDw== +"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.12.1", "@babel/types@^7.12.5", "@babel/types@^7.12.6", "@babel/types@^7.12.7", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": + version "7.12.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.7.tgz#6039ff1e242640a29452c9ae572162ec9a8f5d13" + integrity sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ== dependencies: "@babel/helper-validator-identifier" "^7.10.4" lodash "^4.17.19" to-fast-properties "^2.0.0" +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + "@choojs/findup@^0.2.0": version "0.2.1" resolved "https://registry.yarnpkg.com/@choojs/findup/-/findup-0.2.1.tgz#ac13c59ae7be6e1da64de0779a0a7f03d75615a3" @@ -879,17 +1146,258 @@ dependencies: commander "^2.15.1" -"@fullhuman/postcss-purgecss@^3.0.0": - version "3.1.3" - resolved "https://registry.yarnpkg.com/@fullhuman/postcss-purgecss/-/postcss-purgecss-3.1.3.tgz#47af7b87c9bfb3de4bc94a38f875b928fffdf339" - integrity sha512-kwOXw8fZ0Lt1QmeOOrd+o4Ibvp4UTEBFQbzvWldjlKv5n+G9sXfIPn1hh63IQIL8K8vbvv1oYMJiIUbuy9bGaA== +"@cnakazawa/watch@^1.0.3": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" + integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== dependencies: - purgecss "^3.1.3" + exec-sh "^0.3.2" + minimist "^1.2.0" -"@iarna/toml@^2.2.0": - version "2.2.5" - resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c" - integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg== +"@csstools/convert-colors@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" + integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== + +"@csstools/normalize.css@^10.1.0": + version "10.1.0" + resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18" + integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg== + +"@eslint/eslintrc@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.2.1.tgz#f72069c330461a06684d119384435e12a5d76e3c" + integrity sha512-XRUeBZ5zBWLYgSANMpThFddrZZkEbGHgUdt5UJjZfnlN9BGCiUBrf+nvbRupSjMvqzwnQN0qwCmOxITt1cfywA== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + lodash "^4.17.19" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + +"@hapi/address@2.x.x": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" + integrity sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ== + +"@hapi/bourne@1.x.x": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-1.3.2.tgz#0a7095adea067243ce3283e1b56b8a8f453b242a" + integrity sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA== + +"@hapi/hoek@8.x.x", "@hapi/hoek@^8.3.0": + version "8.5.1" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.5.1.tgz#fde96064ca446dec8c55a8c2f130957b070c6e06" + integrity sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow== + +"@hapi/joi@^15.1.0": + version "15.1.1" + resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-15.1.1.tgz#c675b8a71296f02833f8d6d243b34c57b8ce19d7" + integrity sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ== + dependencies: + "@hapi/address" "2.x.x" + "@hapi/bourne" "1.x.x" + "@hapi/hoek" "8.x.x" + "@hapi/topo" "3.x.x" + +"@hapi/topo@3.x.x": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-3.1.6.tgz#68d935fa3eae7fdd5ab0d7f953f3205d8b2bfc29" + integrity sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ== + dependencies: + "@hapi/hoek" "^8.3.0" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" + integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== + +"@jest/console@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" + integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^26.6.2" + jest-util "^26.6.2" + slash "^3.0.0" + +"@jest/core@^26.6.0", "@jest/core@^26.6.3": + version "26.6.3" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad" + integrity sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw== + dependencies: + "@jest/console" "^26.6.2" + "@jest/reporters" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-changed-files "^26.6.2" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-resolve-dependencies "^26.6.3" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + jest-watcher "^26.6.2" + micromatch "^4.0.2" + p-each-series "^2.1.0" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^26.6.0", "@jest/environment@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c" + integrity sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA== + dependencies: + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + +"@jest/fake-timers@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" + integrity sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA== + dependencies: + "@jest/types" "^26.6.2" + "@sinonjs/fake-timers" "^6.0.1" + "@types/node" "*" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" + jest-util "^26.6.2" + +"@jest/globals@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" + integrity sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA== + dependencies: + "@jest/environment" "^26.6.2" + "@jest/types" "^26.6.2" + expect "^26.6.2" + +"@jest/reporters@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" + integrity sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.2" + graceful-fs "^4.2.4" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^4.0.3" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + jest-haste-map "^26.6.2" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" + slash "^3.0.0" + source-map "^0.6.0" + string-length "^4.0.1" + terminal-link "^2.0.0" + v8-to-istanbul "^7.0.0" + optionalDependencies: + node-notifier "^8.0.0" + +"@jest/source-map@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" + integrity sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.2.4" + source-map "^0.6.0" + +"@jest/test-result@^26.6.0", "@jest/test-result@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" + integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== + dependencies: + "@jest/console" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^26.6.3": + version "26.6.3" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17" + integrity sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw== + dependencies: + "@jest/test-result" "^26.6.2" + graceful-fs "^4.2.4" + jest-haste-map "^26.6.2" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" + +"@jest/transform@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" + integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^26.6.2" + babel-plugin-istanbul "^6.0.0" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.4" + jest-haste-map "^26.6.2" + jest-regex-util "^26.0.0" + jest-util "^26.6.2" + micromatch "^4.0.2" + pirates "^4.0.1" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + +"@jest/types@^26.6.0", "@jest/types@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" + integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" "@mapbox/geojson-rewind@^0.5.0": version "0.5.0" @@ -941,14 +1449,6 @@ resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe" integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q== -"@mrmlnc/readdir-enhanced@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" - integrity sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g== - dependencies: - call-me-maybe "^1.0.1" - glob-to-regexp "^0.3.0" - "@nodelib/fs.scandir@2.1.3": version "2.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" @@ -962,11 +1462,6 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== -"@nodelib/fs.stat@^1.1.2": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" - integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== - "@nodelib/fs.walk@^1.2.3": version "1.2.4" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" @@ -975,46 +1470,12 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" -"@parcel/fs@^1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@parcel/fs/-/fs-1.11.0.tgz#fb8a2be038c454ad46a50dc0554c1805f13535cd" - integrity sha512-86RyEqULbbVoeo8OLcv+LQ1Vq2PKBAvWTU9fCgALxuCTbbs5Ppcvll4Vr+Ko1AnmMzja/k++SzNAwJfeQXVlpA== +"@npmcli/move-file@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.0.1.tgz#de103070dac0f48ce49cf6693c23af59c0f70464" + integrity sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw== dependencies: - "@parcel/utils" "^1.11.0" - mkdirp "^0.5.1" - rimraf "^2.6.2" - -"@parcel/logger@^1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-1.11.1.tgz#c55b0744bcbe84ebc291155627f0ec406a23e2e6" - integrity sha512-9NF3M6UVeP2udOBDILuoEHd8VrF4vQqoWHEafymO1pfSoOMfxrSJZw1MfyAAIUN/IFp9qjcpDCUbDZB+ioVevA== - dependencies: - "@parcel/workers" "^1.11.0" - chalk "^2.1.0" - grapheme-breaker "^0.3.2" - ora "^2.1.0" - strip-ansi "^4.0.0" - -"@parcel/utils@^1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-1.11.0.tgz#539e08fff8af3b26eca11302be80b522674b51ea" - integrity sha512-cA3p4jTlaMeOtAKR/6AadanOPvKeg8VwgnHhOyfi0yClD0TZS/hi9xu12w4EzA/8NtHu0g6o4RDfcNjqN8l1AQ== - -"@parcel/watcher@^1.12.1": - version "1.12.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-1.12.1.tgz#b98b3df309fcab93451b5583fc38e40826696dad" - integrity sha512-od+uCtCxC/KoNQAIE1vWx1YTyKYY+7CTrxBJPRh3cDWw/C0tCtlBMVlrbplscGoEpt6B27KhJDCv82PBxOERNA== - dependencies: - "@parcel/utils" "^1.11.0" - chokidar "^2.1.5" - -"@parcel/workers@^1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@parcel/workers/-/workers-1.11.0.tgz#7b8dcf992806f4ad2b6cecf629839c41c2336c59" - integrity sha512-USSjRAAQYsZFlv43FUPdD+jEGML5/8oLF0rUzPQTtK4q9kvaXr49F5ZplyLz5lox78cLZ0TxN2bIDQ1xhOkulQ== - dependencies: - "@parcel/utils" "^1.11.0" - physical-cpu-count "^2.0.0" + mkdirp "^1.0.4" "@plotly/d3-sankey-circular@0.33.1": version "0.33.1" @@ -1051,6 +1512,214 @@ parse-rect "^1.2.0" pick-by-alias "^1.2.0" +"@pmmmwh/react-refresh-webpack-plugin@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.4.2.tgz#1f9741e0bde9790a0e13272082ed7272a083620d" + integrity sha512-Loc4UDGutcZ+Bd56hBInkm6JyjyCwWy4t2wcDXzN8EDPANgVRj0VP8Nxn0Zq2pc+WKauZwEivQgbDGg4xZO20A== + dependencies: + ansi-html "^0.0.7" + error-stack-parser "^2.0.6" + html-entities "^1.2.1" + native-url "^0.2.6" + schema-utils "^2.6.5" + source-map "^0.7.3" + +"@rollup/plugin-node-resolve@^7.1.1": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz#80de384edfbd7bfc9101164910f86078151a3eca" + integrity sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q== + dependencies: + "@rollup/pluginutils" "^3.0.8" + "@types/resolve" "0.0.8" + builtin-modules "^3.1.0" + is-module "^1.0.0" + resolve "^1.14.2" + +"@rollup/plugin-replace@^2.3.1": + version "2.3.4" + resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-2.3.4.tgz#7dd84c17755d62b509577f2db37eb524d7ca88ca" + integrity sha512-waBhMzyAtjCL1GwZes2jaE9MjuQ/DQF2BatH3fRivUF3z0JBFrU0U6iBNC/4WR+2rLKhaAhPWDNPYp4mI6RqdQ== + dependencies: + "@rollup/pluginutils" "^3.1.0" + magic-string "^0.25.7" + +"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" + integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== + dependencies: + "@types/estree" "0.0.39" + estree-walker "^1.0.1" + picomatch "^2.2.2" + +"@sinonjs/commons@^1.7.0": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217" + integrity sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" + integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@surma/rollup-plugin-off-main-thread@^1.1.1": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-1.4.2.tgz#e6786b6af5799f82f7ab3a82e53f6182d2b91a58" + integrity sha512-yBMPqmd1yEJo/280PAMkychuaALyQ9Lkb5q1ck3mjJrFuEobIfhnQ4J3mbvBoISmR3SWMWV+cGB/I0lCQee79A== + dependencies: + ejs "^2.6.1" + magic-string "^0.25.0" + +"@svgr/babel-plugin-add-jsx-attribute@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz#81ef61947bb268eb9d50523446f9c638fb355906" + integrity sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg== + +"@svgr/babel-plugin-remove-jsx-attribute@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz#6b2c770c95c874654fd5e1d5ef475b78a0a962ef" + integrity sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg== + +"@svgr/babel-plugin-remove-jsx-empty-expression@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz#25621a8915ed7ad70da6cea3d0a6dbc2ea933efd" + integrity sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA== + +"@svgr/babel-plugin-replace-jsx-attribute-value@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz#0b221fc57f9fcd10e91fe219e2cd0dd03145a897" + integrity sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ== + +"@svgr/babel-plugin-svg-dynamic-title@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz#139b546dd0c3186b6e5db4fefc26cb0baea729d7" + integrity sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg== + +"@svgr/babel-plugin-svg-em-dimensions@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz#6543f69526632a133ce5cabab965deeaea2234a0" + integrity sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw== + +"@svgr/babel-plugin-transform-react-native-svg@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz#00bf9a7a73f1cad3948cdab1f8dfb774750f8c80" + integrity sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q== + +"@svgr/babel-plugin-transform-svg-component@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz#583a5e2a193e214da2f3afeb0b9e8d3250126b4a" + integrity sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ== + +"@svgr/babel-preset@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-5.5.0.tgz#8af54f3e0a8add7b1e2b0fcd5a882c55393df327" + integrity sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "^5.4.0" + "@svgr/babel-plugin-remove-jsx-attribute" "^5.4.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "^5.0.1" + "@svgr/babel-plugin-replace-jsx-attribute-value" "^5.0.1" + "@svgr/babel-plugin-svg-dynamic-title" "^5.4.0" + "@svgr/babel-plugin-svg-em-dimensions" "^5.4.0" + "@svgr/babel-plugin-transform-react-native-svg" "^5.4.0" + "@svgr/babel-plugin-transform-svg-component" "^5.5.0" + +"@svgr/core@^5.4.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-5.5.0.tgz#82e826b8715d71083120fe8f2492ec7d7874a579" + integrity sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ== + dependencies: + "@svgr/plugin-jsx" "^5.5.0" + camelcase "^6.2.0" + cosmiconfig "^7.0.0" + +"@svgr/hast-util-to-babel-ast@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz#5ee52a9c2533f73e63f8f22b779f93cd432a5461" + integrity sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ== + dependencies: + "@babel/types" "^7.12.6" + +"@svgr/plugin-jsx@^5.4.0", "@svgr/plugin-jsx@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz#1aa8cd798a1db7173ac043466d7b52236b369000" + integrity sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA== + dependencies: + "@babel/core" "^7.12.3" + "@svgr/babel-preset" "^5.5.0" + "@svgr/hast-util-to-babel-ast" "^5.5.0" + svg-parser "^2.0.2" + +"@svgr/plugin-svgo@^5.4.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz#02da55d85320549324e201c7b2e53bf431fcc246" + integrity sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ== + dependencies: + cosmiconfig "^7.0.0" + deepmerge "^4.2.2" + svgo "^1.2.2" + +"@svgr/webpack@5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-5.4.0.tgz#b68bc86e29cf007292b96ced65f80971175632e0" + integrity sha512-LjepnS/BSAvelnOnnzr6Gg0GcpLmnZ9ThGFK5WJtm1xOqdBE/1IACZU7MMdVzjyUkfFqGz87eRE4hFaSLiUwYg== + dependencies: + "@babel/core" "^7.9.0" + "@babel/plugin-transform-react-constant-elements" "^7.9.0" + "@babel/preset-env" "^7.9.5" + "@babel/preset-react" "^7.9.4" + "@svgr/core" "^5.4.0" + "@svgr/plugin-jsx" "^5.4.0" + "@svgr/plugin-svgo" "^5.4.0" + loader-utils "^2.0.0" + +"@testing-library/dom@^7.28.1": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.29.0.tgz#60b18065bab50a5cde21fe80275a47a43024d9cc" + integrity sha512-0hhuJSmw/zLc6ewR9cVm84TehuTd7tbqBX9pRNSp8znJ9gTmSgesdbiGZtt8R6dL+2rgaPFp9Yjr7IU1HWm49w== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^4.2.0" + aria-query "^4.2.2" + chalk "^4.1.0" + dom-accessibility-api "^0.5.4" + lz-string "^1.4.4" + pretty-format "^26.6.2" + +"@testing-library/jest-dom@^5.11.4": + version "5.11.8" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.11.8.tgz#433a84d6f9a089485101b9e112ef03e5c30bcbfc" + integrity sha512-ScyKrWQM5xNcr79PkSewnA79CLaoxVskE+f7knTOhDD9ftZSA1Jw8mj+pneqhEu3x37ncNfW84NUr7lqK+mXjA== + dependencies: + "@babel/runtime" "^7.9.2" + "@types/testing-library__jest-dom" "^5.9.1" + aria-query "^4.2.2" + chalk "^3.0.0" + css "^3.0.0" + css.escape "^1.5.1" + lodash "^4.17.15" + redent "^3.0.0" + +"@testing-library/react@^11.1.0": + version "11.2.2" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.2.tgz#099c6c195140ff069211143cb31c0f8337bdb7b7" + integrity sha512-jaxm0hwUjv+hzC+UFEywic7buDC9JQ1q3cDsrWVSDAPmLotfA6E6kUHlYm/zOeGCac6g48DR36tFHxl7Zb+N5A== + dependencies: + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^7.28.1" + +"@testing-library/user-event@^12.1.10": + version "12.6.0" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-12.6.0.tgz#2d0229e399eb5a0c6c112e848611432356cac886" + integrity sha512-FNEH/HLmOk5GO70I52tKjs7WvGYckeE/SrnLX/ip7z2IGbffyd5zOUM1tZ10vsTphqm+VbDFI0oaXu0wcfQsAQ== + dependencies: + "@babel/runtime" "^7.12.5" + "@turf/area@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@turf/area/-/area-6.0.1.tgz#50ed63c70ef2bdb72952384f1594319d94f3b051" @@ -1087,10 +1756,48 @@ dependencies: "@turf/helpers" "6.x" -"@types/classnames@^2.2.11": - version "2.2.11" - resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.11.tgz#2521cc86f69d15c5b90664e4829d84566052c1cf" - integrity sha512-2koNhpWm3DgWRp5tpkiJ8JGc1xTn2q0l+jUNUE7oMKXUf5NpI9AIdC4kbjGNFBdHtcxBD18LAksoudAVhFKCjw== +"@types/anymatch@*": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" + integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== + +"@types/aria-query@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0" + integrity sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A== + +"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": + version "7.1.12" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.12.tgz#4d8e9e51eb265552a7e4f1ff2219ab6133bdfb2d" + integrity sha512-wMTHiiTiBAAPebqaPiPDLFA4LYPKr6Ph0Xq/6rq1Ur3v66HXyG+clfR9CNETkD7MQS8ZHvpQOtA53DLws5WAEQ== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.2.tgz#f3d71178e187858f7c45e30380f8f1b7415a12d8" + integrity sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.0.tgz#0c888dd70b3ee9eebb6e4f200e809da0076262be" + integrity sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.15.tgz#db9e4238931eb69ef8aab0ad6523d4d4caa39d03" + integrity sha512-Pzh9O3sTK8V6I1olsXpCfj2k/ygO2q1X0vhhnDrEQyYLHZesWz+zMZMVcwXLCYf0U36EtmyYaFGPfXlTtDHe3A== + dependencies: + "@babel/types" "^7.3.0" "@types/component-emitter@^1.2.10": version "1.2.10" @@ -1102,11 +1809,106 @@ resolved "https://registry.yarnpkg.com/@types/d3/-/d3-3.5.44.tgz#28635f8bb9adf1cef5bbef2b06778174a9d34383" integrity sha512-hFEcf03YGJ2uQoDYEp3nFD5mXWxly5kf6KOstuOQFEs9sUCN7kNlKhcYkpZ3gK6PiHz4XRLkoHa80NVCJNeLBw== +"@types/eslint@^7.2.4": + version "7.2.5" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.5.tgz#92172ecf490c2fce4b076739693d75f30376d610" + integrity sha512-Dc6ar9x16BdaR3NSxSF7T4IjL9gxxViJq8RmFd+2UAyA+K6ck2W+gUwfgpG/y9TPyUuBL35109bbULpEynvltA== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*": + version "0.0.45" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" + integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g== + +"@types/estree@0.0.39": + version "0.0.39" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" + integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== + +"@types/glob@^7.1.1": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" + integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/graceful-fs@^4.1.2": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.4.tgz#4ff9f641a7c6d1a3508ff88bc3141b152772e753" + integrity sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg== + dependencies: + "@types/node" "*" + +"@types/html-minifier-terser@^5.0.0": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#3c9ee980f1a10d6021ae6632ca3e79ca2ec4fb50" + integrity sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA== + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" + integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821" + integrity sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@*", "@types/jest@^26.0.19": + version "26.0.19" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.19.tgz#e6fa1e3def5842ec85045bd5210e9bb8289de790" + integrity sha512-jqHoirTG61fee6v6rwbnEuKhpSKih0tuhqeFbCmMmErhtu3BYlOZaXWjffgOstMM4S/3iQD31lI5bGLTrs97yQ== + dependencies: + jest-diff "^26.0.0" + pretty-format "^26.0.0" + +"@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": + version "7.0.6" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" + integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + "@types/luxon@^1.25.0": version "1.25.0" resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-1.25.0.tgz#3d6fe591fac874f48dd225cb5660b2b785a21a05" integrity sha512-iIJp2CP6C32gVqI08HIYnzqj55tlLnodIBMCcMf28q9ckqMfMzocCmIzd9JWI/ALLPMUiTkCu1JGv3FFtu6t3g== +"@types/minimatch@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== + +"@types/node@*": + version "14.14.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.9.tgz#04afc9a25c6ff93da14deabd65dc44485b53c8d6" + integrity sha512-JsoLXFppG62tWTklIoO4knA+oDTYsmqWxHRvd4lpmfQRNhX6osheUOWETP2jMoV/2bEHuMra8Pp3Dmo/stBFcw== + +"@types/node@^14.14.16": + version "14.14.16" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.16.tgz#3cc351f8d48101deadfed4c9e4f116048d437b4b" + integrity sha512-naXYePhweTi+BMv11TgioE2/FXU4fSl29HAH1ffxVciNsH3rYXjNP2yM8wqmSm7jS20gM8TIklKiTen+1iVncw== + +"@types/normalize-package-data@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" + integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -1119,6 +1921,11 @@ dependencies: "@types/d3" "^3" +"@types/prettier@^2.0.0": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.5.tgz#b6ab3bba29e16b821d84e09ecfaded462b816b00" + integrity sha512-UEyp8LwZ4Dg30kVU2Q3amHHyTn1jEdhCIE59ANed76GaT1Vp76DD3ZWSAxgCrw6wJ0TqeoBpqmfUHiUDPs//HQ== + "@types/prop-types@*": version "15.7.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" @@ -1151,15 +1958,7 @@ "@types/plotly.js" "*" "@types/react" "*" -"@types/react@*": - version "16.14.2" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.2.tgz#85dcc0947d0645349923c04ccef6018a1ab7538c" - integrity sha512-BzzcAlyDxXl2nANlabtT4thtvbbnhee8hMmH/CcJrISDBVcJS1iOsP1f0OAgSdGE0MsY9tqcrb9YoZcOFv9dbQ== - dependencies: - "@types/prop-types" "*" - csstype "^3.0.2" - -"@types/react@^17.0.0": +"@types/react@*", "@types/react@^17.0.0": version "17.0.0" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.0.tgz#5af3eb7fad2807092f0046a1302b7823e27919b8" integrity sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw== @@ -1167,10 +1966,336 @@ "@types/prop-types" "*" csstype "^3.0.2" -"@types/socket.io-client@^1.4.34": - version "1.4.34" - resolved "https://registry.yarnpkg.com/@types/socket.io-client/-/socket.io-client-1.4.34.tgz#8ca5f5732a9ad92b79aba71083cda5e5821e3ed9" - integrity sha512-Lzia5OTQFJZJ5R4HsEEldywiiqT9+W2rDbyHJiiTGqOcju89sCsQ8aUXDljY6Ls33wKZZGC0bfMhr/VpOyjtXg== +"@types/resolve@0.0.8": + version "0.0.8" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" + integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ== + dependencies: + "@types/node" "*" + +"@types/source-list-map@*": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" + integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== + +"@types/stack-utils@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" + integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw== + +"@types/tapable@*", "@types/tapable@^1.0.5": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74" + integrity sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA== + +"@types/testing-library__jest-dom@^5.9.1": + version "5.9.5" + resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.5.tgz#5bf25c91ad2d7b38f264b12275e5c92a66d849b0" + integrity sha512-ggn3ws+yRbOHog9GxnXiEZ/35Mow6YtPZpd7Z5mKDeZS/o7zx3yAle0ov/wjhVB5QT4N2Dt+GNoGCdqkBGCajQ== + dependencies: + "@types/jest" "*" + +"@types/uglify-js@*": + version "3.11.1" + resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.11.1.tgz#97ff30e61a0aa6876c270b5f538737e2d6ab8ceb" + integrity sha512-7npvPKV+jINLu1SpSYVWG8KvyJBhBa8tmzMMdDoVc2pWUYHN8KIXlPJhjJ4LT97c4dXJA2SHL/q6ADbDriZN+Q== + dependencies: + source-map "^0.6.1" + +"@types/webpack-sources@*": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-2.0.0.tgz#08216ab9be2be2e1499beaebc4d469cec81e82a7" + integrity sha512-a5kPx98CNFRKQ+wqawroFunvFqv7GHm/3KOI52NY9xWADgc8smu4R6prt4EU/M4QfVjvgBkMqU4fBhw3QfMVkg== + dependencies: + "@types/node" "*" + "@types/source-list-map" "*" + source-map "^0.7.3" + +"@types/webpack@^4.41.8": + version "4.41.25" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.25.tgz#4d3b5aecc4e44117b376280fbfd2dc36697968c4" + integrity sha512-cr6kZ+4m9lp86ytQc1jPOJXgINQyz3kLLunZ57jznW+WIAL0JqZbGubQk4GlD42MuQL5JGOABrxdpqqWeovlVQ== + dependencies: + "@types/anymatch" "*" + "@types/node" "*" + "@types/tapable" "*" + "@types/uglify-js" "*" + "@types/webpack-sources" "*" + source-map "^0.6.0" + +"@types/yargs-parser@*": + version "15.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" + integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== + +"@types/yargs@^15.0.0": + version "15.0.10" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.10.tgz#0fe3c8173a0d5c3e780b389050140c3f5ea6ea74" + integrity sha512-z8PNtlhrj7eJNLmrAivM7rjBESG6JwC5xP3RVk12i/8HVP7Xnx/sEmERnRImyEuUaJfO942X0qMOYsoupaJbZQ== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^4.5.0": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.8.1.tgz#b362abe0ee478a6c6d06c14552a6497f0b480769" + integrity sha512-d7LeQ7dbUrIv5YVFNzGgaW3IQKMmnmKFneRWagRlGYOSfLJVaRbj/FrBNOBC1a3tVO+TgNq1GbHvRtg1kwL0FQ== + dependencies: + "@typescript-eslint/experimental-utils" "4.8.1" + "@typescript-eslint/scope-manager" "4.8.1" + debug "^4.1.1" + functional-red-black-tree "^1.0.1" + regexpp "^3.0.0" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/experimental-utils@4.8.1", "@typescript-eslint/experimental-utils@^4.0.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.8.1.tgz#27275c20fa4336df99ebcf6195f7d7aa7aa9f22d" + integrity sha512-WigyLn144R3+lGATXW4nNcDJ9JlTkG8YdBWHkDlN0lC3gUGtDi7Pe3h5GPvFKMcRz8KbZpm9FJV9NTW8CpRHpg== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/scope-manager" "4.8.1" + "@typescript-eslint/types" "4.8.1" + "@typescript-eslint/typescript-estree" "4.8.1" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/experimental-utils@^3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz#e179ffc81a80ebcae2ea04e0332f8b251345a686" + integrity sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/types" "3.10.1" + "@typescript-eslint/typescript-estree" "3.10.1" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/parser@^4.5.0": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.8.1.tgz#4fe2fbdbb67485bafc4320b3ae91e34efe1219d1" + integrity sha512-QND8XSVetATHK9y2Ltc/XBl5Ro7Y62YuZKnPEwnNPB8E379fDsvzJ1dMJ46fg/VOmk0hXhatc+GXs5MaXuL5Uw== + dependencies: + "@typescript-eslint/scope-manager" "4.8.1" + "@typescript-eslint/types" "4.8.1" + "@typescript-eslint/typescript-estree" "4.8.1" + debug "^4.1.1" + +"@typescript-eslint/scope-manager@4.8.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.8.1.tgz#e343c475f8f1d15801b546cb17d7f309b768fdce" + integrity sha512-r0iUOc41KFFbZdPAdCS4K1mXivnSZqXS5D9oW+iykQsRlTbQRfuFRSW20xKDdYiaCoH+SkSLeIF484g3kWzwOQ== + dependencies: + "@typescript-eslint/types" "4.8.1" + "@typescript-eslint/visitor-keys" "4.8.1" + +"@typescript-eslint/types@3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727" + integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ== + +"@typescript-eslint/types@4.8.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.8.1.tgz#23829c73c5fc6f4fcd5346a7780b274f72fee222" + integrity sha512-ave2a18x2Y25q5K05K/U3JQIe2Av4+TNi/2YuzyaXLAsDx6UZkz1boZ7nR/N6Wwae2PpudTZmHFXqu7faXfHmA== + +"@typescript-eslint/typescript-estree@3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz#fd0061cc38add4fad45136d654408569f365b853" + integrity sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w== + dependencies: + "@typescript-eslint/types" "3.10.1" + "@typescript-eslint/visitor-keys" "3.10.1" + debug "^4.1.1" + glob "^7.1.6" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/typescript-estree@4.8.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.8.1.tgz#7307e3f2c9e95df7daa8dc0a34b8c43b7ec0dd32" + integrity sha512-bJ6Fn/6tW2g7WIkCWh3QRlaSU7CdUUK52shx36/J7T5oTQzANvi6raoTsbwGM11+7eBbeem8hCCKbyvAc0X3sQ== + dependencies: + "@typescript-eslint/types" "4.8.1" + "@typescript-eslint/visitor-keys" "4.8.1" + debug "^4.1.1" + globby "^11.0.1" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/visitor-keys@3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz#cd4274773e3eb63b2e870ac602274487ecd1e931" + integrity sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ== + dependencies: + eslint-visitor-keys "^1.1.0" + +"@typescript-eslint/visitor-keys@4.8.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.8.1.tgz#794f68ee292d1b2e3aa9690ebedfcb3a8c90e3c3" + integrity sha512-3nrwXFdEYALQh/zW8rFwP4QltqsanCDz4CwWMPiIZmwlk9GlvBeueEIbq05SEq4ganqM0g9nh02xXgv5XI3PeQ== + dependencies: + "@typescript-eslint/types" "4.8.1" + eslint-visitor-keys "^2.0.0" + +"@webassemblyjs/ast@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" + integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== + dependencies: + "@webassemblyjs/helper-module-context" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/wast-parser" "1.9.0" + +"@webassemblyjs/floating-point-hex-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" + integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== + +"@webassemblyjs/helper-api-error@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" + integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== + +"@webassemblyjs/helper-buffer@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" + integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== + +"@webassemblyjs/helper-code-frame@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27" + integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== + dependencies: + "@webassemblyjs/wast-printer" "1.9.0" + +"@webassemblyjs/helper-fsm@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8" + integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== + +"@webassemblyjs/helper-module-context@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07" + integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== + dependencies: + "@webassemblyjs/ast" "1.9.0" + +"@webassemblyjs/helper-wasm-bytecode@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" + integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== + +"@webassemblyjs/helper-wasm-section@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" + integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + +"@webassemblyjs/ieee754@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" + integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" + integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" + integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== + +"@webassemblyjs/wasm-edit@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" + integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/helper-wasm-section" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + "@webassemblyjs/wasm-opt" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + "@webassemblyjs/wast-printer" "1.9.0" + +"@webassemblyjs/wasm-gen@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" + integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/ieee754" "1.9.0" + "@webassemblyjs/leb128" "1.9.0" + "@webassemblyjs/utf8" "1.9.0" + +"@webassemblyjs/wasm-opt@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" + integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + +"@webassemblyjs/wasm-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" + integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-api-error" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/ieee754" "1.9.0" + "@webassemblyjs/leb128" "1.9.0" + "@webassemblyjs/utf8" "1.9.0" + +"@webassemblyjs/wast-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914" + integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/floating-point-hex-parser" "1.9.0" + "@webassemblyjs/helper-api-error" "1.9.0" + "@webassemblyjs/helper-code-frame" "1.9.0" + "@webassemblyjs/helper-fsm" "1.9.0" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/wast-printer@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" + integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/wast-parser" "1.9.0" + "@xtuc/long" "4.2.2" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== a-big-triangle@^1.0.3: version "1.0.3" @@ -1181,7 +2306,7 @@ a-big-triangle@^1.0.3: gl-vao "^1.2.0" weak-map "^1.0.5" -abab@^2.0.0: +abab@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== @@ -1191,39 +2316,38 @@ abs-svg-path@^0.1.1, abs-svg-path@~0.1.1: resolved "https://registry.yarnpkg.com/abs-svg-path/-/abs-svg-path-0.1.1.tgz#df601c8e8d2ba10d4a76d625e236a9a39c2723bf" integrity sha1-32Acjo0roQ1KdtYl4japo5wnI78= -acorn-globals@^4.3.0: - version "4.3.4" - resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" - integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== dependencies: - acorn "^6.0.1" - acorn-walk "^6.0.1" + mime-types "~2.1.24" + negotiator "0.6.2" -acorn-node@^1.6.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" - integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== dependencies: - acorn "^7.0.0" - acorn-walk "^7.0.0" - xtend "^4.0.2" + acorn "^7.1.1" + acorn-walk "^7.1.1" -acorn-walk@^6.0.1: - version "6.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" - integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== +acorn-jsx@^5.2.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" + integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== -acorn-walk@^7.0.0: +acorn-walk@^7.1.1: version "7.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== -acorn@^6.0.1, acorn@^6.0.4: +acorn@^6.4.1: version "6.4.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== -acorn@^7.0.0, acorn@^7.1.1: +acorn@^7.1.0, acorn@^7.1.1, acorn@^7.4.0: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== @@ -1235,6 +2359,19 @@ add-line-numbers@^1.0.1: dependencies: pad-left "^1.0.2" +address@1.1.2, address@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" + integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA== + +adjust-sourcemap-loader@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-3.0.0.tgz#5ae12fb5b7b1c585e80bbb5a63ec163a1a45e61e" + integrity sha512-YBrGyT2/uVQ/c6Rr+t6ZJXniY03YtHGMJQYal368burRGYKqhx9qGTWqcBU5s1CwYY9E/ri63RYyG1IacMZtqw== + dependencies: + loader-utils "^2.0.0" + regex-parser "^2.2.11" + affine-hull@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/affine-hull/-/affine-hull-1.0.0.tgz#763ff1d38d063ceb7e272f17ee4d7bbcaf905c5d" @@ -1242,7 +2379,25 @@ affine-hull@^1.0.0: dependencies: robust-orientation "^1.1.3" -ajv@^6.12.3: +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv-errors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" + integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== + +ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -1278,27 +2433,44 @@ alphanum-sort@^1.0.0: resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= +ansi-colors@^3.0.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" + integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== + +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-escapes@^4.2.1, ansi-escapes@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" + integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== + dependencies: + type-fest "^0.11.0" + +ansi-html@0.0.7, ansi-html@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" + integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= + ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== ansi-regex@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= - -ansi-styles@^3.2.1: +ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== @@ -1312,13 +2484,6 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-to-html@^0.6.4: - version "0.6.14" - resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.14.tgz#65fe6d08bba5dd9db33f44a20aec331e0010dad8" - integrity sha512-7ZslfB1+EnFSDO5Ju+ue5Y6It19DRnZXWv8jrGHgIlPna5Mh4jz7BV5jCbQneXNFurQcKoolaaAjHtgSBfOIuA== - dependencies: - entities "^1.1.2" - anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -1327,7 +2492,7 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -anymatch@~3.1.1: +anymatch@^3.0.3, anymatch@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== @@ -1335,6 +2500,11 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +aproba@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -1342,6 +2512,19 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +aria-query@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" + integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== + dependencies: + "@babel/runtime" "^7.10.2" + "@babel/runtime-corejs3" "^7.10.2" + +arity-n@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/arity-n/-/arity-n-1.0.4.tgz#d9e76b11733e08569c0847ae7b39b2860b30b745" + integrity sha1-2edrEXM+CFacCEeuezmyhgswt0U= + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -1362,10 +2545,29 @@ array-bounds@^1.0.0, array-bounds@^1.0.1: resolved "https://registry.yarnpkg.com/array-bounds/-/array-bounds-1.0.1.tgz#da11356b4e18e075a4f0c86e1f179a67b7d7ea31" integrity sha512-8wdW3ZGk6UjMPJx/glyEt0sLzzwAE1bhToPsO1W2pbpR2gULyxe3BjSiuJFheP50T/GgODVPz2fuMUmIywt8cQ== -array-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" - integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= +array-find-index@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + +array-flatten@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" + integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== + +array-includes@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" + integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0" + is-string "^1.0.5" array-normalize@^1.1.4: version "1.1.4" @@ -1384,16 +2586,57 @@ array-rearrange@^2.2.2: resolved "https://registry.yarnpkg.com/array-rearrange/-/array-rearrange-2.2.2.tgz#fa1a2acf8d02e88dd0c9602aa0e06a79158b2283" integrity sha512-UfobP5N12Qm4Qu4fwLDIi2v6+wZsSf6snYSxAMeKhrh37YGnNWZPRmVEKc/2wfms53TLQnzfpG8wCx2Y/6NG1w== +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= + dependencies: + array-uniq "^1.0.1" + array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +array-uniq@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= + array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= +array.prototype.flat@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz#6ef638b43312bd401b4c6199fdec7e2dc9e9a123" + integrity sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + +array.prototype.flatmap@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz#94cfd47cc1556ec0747d97f7c7738c58122004c9" + integrity sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + function-bind "^1.1.1" + +arrify@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" + integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== + +asap@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + asn1.js@^5.2.0: version "5.4.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" @@ -1429,6 +2672,16 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= +ast-types-flow@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" + integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + async-each@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" @@ -1439,6 +2692,13 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== +async@^2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" + integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== + dependencies: + lodash "^4.17.14" + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1464,16 +2724,17 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -autoprefixer@^10.1.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.1.0.tgz#b19fd8524edef8c85c9db3bdb0c998de84e172fb" - integrity sha512-0/lBNwN+ZUnb5su18NZo5MBIjDaq6boQKZcxwy86Gip/CmXA2zZqUoFQLCNAGI5P25ZWSP2RWdhDJ8osfKEjoQ== +autoprefixer@^9.6.1: + version "9.8.6" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" + integrity sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg== dependencies: - browserslist "^4.15.0" - caniuse-lite "^1.0.30001165" + browserslist "^4.12.0" + caniuse-lite "^1.0.30001109" colorette "^1.2.1" - fraction.js "^4.0.12" normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^7.0.32" postcss-value-parser "^4.1.0" aws-sign2@~0.7.0: @@ -1486,6 +2747,60 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== +axe-core@^4.0.2: + version "4.1.1" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.1.1.tgz#70a7855888e287f7add66002211a423937063eaf" + integrity sha512-5Kgy8Cz6LPC9DJcNb3yjAXTu3XihQgEdnIg50c//zOC/MyLP0Clg+Y8Sh9ZjjnvBrDZU4DgXS9C3T9r4/scGZQ== + +axobject-query@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" + integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== + +babel-eslint@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232" + integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.7.0" + "@babel/traverse" "^7.7.0" + "@babel/types" "^7.7.0" + eslint-visitor-keys "^1.0.0" + resolve "^1.12.0" + +babel-extract-comments@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/babel-extract-comments/-/babel-extract-comments-1.0.0.tgz#0a2aedf81417ed391b85e18b4614e693a0351a21" + integrity sha512-qWWzi4TlddohA91bFwgt6zO/J0X+io7Qp184Fw0m2JYRSTZnJbFR8+07KmzudHCZgOiKRCrjhylwv9Xd8gfhVQ== + dependencies: + babylon "^6.18.0" + +babel-jest@^26.6.0, babel-jest@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" + integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA== + dependencies: + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/babel__core" "^7.1.7" + babel-plugin-istanbul "^6.0.0" + babel-preset-jest "^26.6.2" + chalk "^4.0.0" + graceful-fs "^4.2.4" + slash "^3.0.0" + +babel-loader@8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3" + integrity sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw== + dependencies: + find-cache-dir "^2.1.0" + loader-utils "^1.4.0" + mkdirp "^0.5.3" + pify "^4.0.1" + schema-utils "^2.6.5" + babel-plugin-dynamic-import-node@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" @@ -1493,7 +2808,107 @@ babel-plugin-dynamic-import-node@^2.3.3: dependencies: object.assign "^4.1.0" -babel-runtime@^6.11.6, babel-runtime@^6.26.0: +babel-plugin-istanbul@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765" + integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^4.0.0" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d" + integrity sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.0.0" + "@types/babel__traverse" "^7.0.6" + +babel-plugin-macros@2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" + integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== + dependencies: + "@babel/runtime" "^7.7.2" + cosmiconfig "^6.0.0" + resolve "^1.12.0" + +babel-plugin-named-asset-import@^0.3.7: + version "0.3.7" + resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz#156cd55d3f1228a5765774340937afc8398067dd" + integrity sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw== + +babel-plugin-syntax-object-rest-spread@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" + integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U= + +babel-plugin-transform-object-rest-spread@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06" + integrity sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY= + dependencies: + babel-plugin-syntax-object-rest-spread "^6.8.0" + babel-runtime "^6.26.0" + +babel-plugin-transform-react-remove-prop-types@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz#f2edaf9b4c6a5fbe5c1d678bfb531078c1555f3a" + integrity sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA== + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.0.tgz#cf5feef29551253471cfa82fc8e0f5063df07a77" + integrity sha512-mGkvkpocWJes1CmMKtgGUwCeeq0pOhALyymozzDWYomHTbDLwueDYG6p4TK1YOeYHCzBzYPsWkgTto10JubI1Q== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-jest@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee" + integrity sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ== + dependencies: + babel-plugin-jest-hoist "^26.6.2" + babel-preset-current-node-syntax "^1.0.0" + +babel-preset-react-app@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/babel-preset-react-app/-/babel-preset-react-app-10.0.0.tgz#689b60edc705f8a70ce87f47ab0e560a317d7045" + integrity sha512-itL2z8v16khpuKutx5IH8UdCdSTuzrOhRFTEdIhveZ2i1iBKDrVE0ATa4sFVy+02GLucZNVBWtoarXBy0Msdpg== + dependencies: + "@babel/core" "7.12.3" + "@babel/plugin-proposal-class-properties" "7.12.1" + "@babel/plugin-proposal-decorators" "7.12.1" + "@babel/plugin-proposal-nullish-coalescing-operator" "7.12.1" + "@babel/plugin-proposal-numeric-separator" "7.12.1" + "@babel/plugin-proposal-optional-chaining" "7.12.1" + "@babel/plugin-transform-flow-strip-types" "7.12.1" + "@babel/plugin-transform-react-display-name" "7.12.1" + "@babel/plugin-transform-runtime" "7.12.1" + "@babel/preset-env" "7.12.1" + "@babel/preset-react" "7.12.1" + "@babel/preset-typescript" "7.12.1" + "@babel/runtime" "7.12.1" + babel-plugin-macros "2.8.0" + babel-plugin-transform-react-remove-prop-types "0.4.24" + +babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= @@ -1501,24 +2916,10 @@ babel-runtime@^6.11.6, babel-runtime@^6.26.0: core-js "^2.4.0" regenerator-runtime "^0.11.0" -babel-types@^6.15.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" - integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= - dependencies: - babel-runtime "^6.26.0" - esutils "^2.0.2" - lodash "^4.17.4" - to-fast-properties "^1.0.3" - -babylon-walk@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/babylon-walk/-/babylon-walk-1.0.2.tgz#3b15a5ddbb482a78b4ce9c01c8ba181702d9d6ce" - integrity sha1-OxWl3btIKni0zpwByLoYFwLZ1s4= - dependencies: - babel-runtime "^6.11.6" - babel-types "^6.15.0" - lodash.clone "^4.5.0" +babylon@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" + integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== backo2@1.0.2: version "1.0.2" @@ -1560,6 +2961,11 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" +batch@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= + bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -1567,6 +2973,16 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +bfj@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/bfj/-/bfj-7.0.2.tgz#1988ce76f3add9ac2913fd8ba47aad9e651bfbb2" + integrity sha512-+e/UqUzwmzJamNF50tBV6tZPTORow7gQ96iFow+8b562OdMpEK0BcJEq2OSPEDmAbSMBQ7PKZ87ubFkgxpYWgw== + dependencies: + bluebird "^3.5.5" + check-types "^11.1.1" + hoopy "^0.1.4" + tryer "^1.0.1" + big-rat@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/big-rat/-/big-rat-1.0.4.tgz#768d093bb57930dd18ed575c7fca27dc5391adea" @@ -1576,6 +2992,11 @@ big-rat@^1.0.3: bn.js "^4.11.6" double-bits "^1.1.1" +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" @@ -1628,6 +3049,11 @@ bl@^2.2.1: readable-stream "^2.3.5" safe-buffer "^5.1.1" +bluebird@^3.5.5: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.6, bn.js@^4.4.0: version "4.11.9" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" @@ -1638,6 +3064,34 @@ bn.js@^5.0.0, bn.js@^5.1.1: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b" integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ== +body-parser@1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" + integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== + dependencies: + bytes "3.1.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.7.2" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.7.0" + raw-body "2.4.0" + type-is "~1.6.17" + +bonjour@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" + integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU= + dependencies: + array-flatten "^2.1.0" + deep-equal "^1.0.1" + dns-equal "^1.0.0" + dns-txt "^2.0.2" + multicast-dns "^6.0.1" + multicast-dns-service-types "^1.1.0" + boolbase@^1.0.0, boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" @@ -1687,16 +3141,6 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" -brfs@^1.2.0: - version "1.6.1" - resolved "https://registry.yarnpkg.com/brfs/-/brfs-1.6.1.tgz#b78ce2336d818e25eea04a0947cba6d4fb8849c3" - integrity sha512-OfZpABRQQf+Xsmju8XE9bDjs+uU4vLREGolP7bDgcpsI17QREyZ4Bl+2KLxxx1kCgA0fAIhKQBaBYh+PEcCqYQ== - dependencies: - quote-stream "^1.0.1" - resolve "^1.1.5" - static-module "^2.2.0" - through2 "^2.0.0" - brorand@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" @@ -1768,27 +3212,44 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^4.0.0, browserslist@^4.1.0, browserslist@^4.14.5, browserslist@^4.15.0: - version "4.16.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.0.tgz#410277627500be3cb28a1bfe037586fbedf9488b" - integrity sha512-/j6k8R0p3nxOC6kx5JGAxsnhc9ixaWJfYc+TNTzxg6+ARaESAvQGV7h0uNOB4t+pLQJZWzcrMxXOxjgsCj3dqQ== +browserslist@4.14.2: + version "4.14.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.2.tgz#1b3cec458a1ba87588cc5e9be62f19b6d48813ce" + integrity sha512-HI4lPveGKUR0x2StIz+2FXfDk9SfVMrxn6PLh1JeGUwcuoDkdKZebWiyLRJ68iIPDpMI4JLVDf7S7XzslgWOhw== dependencies: - caniuse-lite "^1.0.30001165" - colorette "^1.2.1" - electron-to-chromium "^1.3.621" - escalade "^3.1.1" - node-releases "^1.1.67" + caniuse-lite "^1.0.30001125" + electron-to-chromium "^1.3.564" + escalade "^3.0.2" + node-releases "^1.1.61" -buffer-equal@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" - integrity sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs= +browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.14.6, browserslist@^4.6.2, browserslist@^4.6.4: + version "4.14.7" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.7.tgz#c071c1b3622c1c2e790799a37bb09473a4351cb6" + integrity sha512-BSVRLCeG3Xt/j/1cCGj1019Wbty0H+Yvu2AOuZSuoaUWn3RatbL33Cxk+Q4jRMRAbOm0p7SLravLjpnT6s0vzQ== + dependencies: + caniuse-lite "^1.0.30001157" + colorette "^1.2.1" + electron-to-chromium "^1.3.591" + escalade "^3.1.1" + node-releases "^1.1.66" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer-indexof@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" + integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== + buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -1803,16 +3264,70 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" +builtin-modules@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" + integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw== + builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= -bytes@^3.0.0: +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + +bytes@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== +cacache@^12.0.2: + version "12.0.4" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" + integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== + dependencies: + bluebird "^3.5.5" + chownr "^1.1.1" + figgy-pudding "^3.5.1" + glob "^7.1.4" + graceful-fs "^4.1.15" + infer-owner "^1.0.3" + lru-cache "^5.1.1" + mississippi "^3.0.0" + mkdirp "^0.5.1" + move-concurrently "^1.0.1" + promise-inflight "^1.0.1" + rimraf "^2.6.3" + ssri "^6.0.1" + unique-filename "^1.1.1" + y18n "^4.0.0" + +cacache@^15.0.5: + version "15.0.5" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.5.tgz#69162833da29170d6732334643c60e005f5f17d0" + integrity sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A== + dependencies: + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.0" + tar "^6.0.2" + unique-filename "^1.1.1" + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -1836,11 +3351,6 @@ call-bind@^1.0.0: function-bind "^1.1.1" get-intrinsic "^1.0.0" -call-me-maybe@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" - integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= - caller-callsite@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" @@ -1865,10 +3375,23 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camelcase-css@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" - integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== +camel-case@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.1.tgz#1fc41c854f00e2f7d0139dfeba1542d6896fe547" + integrity sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q== + dependencies: + pascal-case "^3.1.1" + tslib "^1.10.0" + +camelcase@5.3.1, camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.0.0, camelcase@^6.1.0, camelcase@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" + integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== caniuse-api@^3.0.0: version "3.0.0" @@ -1880,10 +3403,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001165: - version "1.0.30001165" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001165.tgz#32955490d2f60290bb186bb754f2981917fa744f" - integrity sha512-8cEsSMwXfx7lWSUMA2s08z9dIgsnR5NAqjXP23stdsU3AUWkCr/rr4s4OFtHXn5XXr6+7kam3QFVoYyXNPdJPA== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001157: + version "1.0.30001159" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001159.tgz#bebde28f893fa9594dadcaa7d6b8e2aa0299df20" + integrity sha512-w9Ph56jOsS8RL20K9cLND3u/+5WASWdhC/PPrf+V3/HsM3uHOavWOR1Xzakbv4Puo/srmPHudkmCRWM7Aq+/UA== canvas-fit@^1.5.0: version "1.5.0" @@ -1892,6 +3415,18 @@ canvas-fit@^1.5.0: dependencies: element-size "^1.1.1" +capture-exit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" + integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== + dependencies: + rsvp "^4.8.4" + +case-sensitive-paths-webpack-plugin@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.3.0.tgz#23ac613cc9a856e4f88ff8bb73bbb5e989825cf7" + integrity sha512-/4YgnZS8y1UXXmC02xD5rRrBEu6T5ub+mQHLNRj0fzTRbgdBYhsNo2V5EqwgqrExjxsjtF/OpAKAMkKsxbD5XQ== + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -1911,18 +3446,7 @@ cell-orientation@^1.0.1: resolved "https://registry.yarnpkg.com/cell-orientation/-/cell-orientation-1.0.1.tgz#b504ad96a66ad286d9edd985a2253d03b80d2850" integrity sha1-tQStlqZq0obZ7dmFoiU9A7gNKFA= -chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1931,6 +3455,14 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4 escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^4.0.0, chalk@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" @@ -1939,7 +3471,17 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chokidar@^2.1.5: +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +check-types@^11.1.1: + version "11.1.2" + resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.1.2.tgz#86a7c12bf5539f6324eb0e70ca8896c0e38f3e2f" + integrity sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ== + +chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== @@ -1958,7 +3500,7 @@ chokidar@^2.1.5: optionalDependencies: fsevents "^1.2.7" -chokidar@^3.3.0: +chokidar@^3.4.1: version "3.4.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b" integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ== @@ -1973,6 +3515,28 @@ chokidar@^3.3.0: optionalDependencies: fsevents "~2.1.2" +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +chrome-trace-event@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" + integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== + dependencies: + tslib "^1.9.0" + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" @@ -1996,6 +3560,11 @@ circumradius@^1.0.0: dependencies: circumcenter "^1.0.0" +cjs-module-lexer@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" + integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw== + clamp@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/clamp/-/clamp-1.0.1.tgz#66a0e64011816e37196828fdc8c8c147312c8634" @@ -2011,10 +3580,12 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@^2.2.6: - version "2.2.6" - resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" - integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== +clean-css@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78" + integrity sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA== + dependencies: + source-map "~0.6.0" clean-pslg@^1.1.0, clean-pslg@^1.1.2: version "1.1.2" @@ -2029,36 +3600,42 @@ clean-pslg@^1.1.0, clean-pslg@^1.1.2: union-find "^1.0.2" uniq "^1.0.1" -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== dependencies: - restore-cursor "^2.0.0" + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" -cli-spinners@^1.1.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.3.1.tgz#002c1990912d0d59580c93bd36c056de99e4259a" - integrity sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg== - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== dependencies: string-width "^4.2.0" strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" + wrap-ansi "^6.2.0" -clone@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" -clone@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" - integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= coa@^2.0.2: version "2.0.2" @@ -2069,6 +3646,11 @@ coa@^2.0.2: chalk "^2.4.1" q "^1.1.2" +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -2188,7 +3770,7 @@ color-string@^1.5.4: color-name "^1.0.0" simple-swizzle "^0.2.2" -color@^3.0.0, color@^3.1.3: +color@^3.0.0: version "3.1.3" resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e" integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ== @@ -2215,25 +3797,25 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -command-exists@^1.2.6: - version "1.2.9" - resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69" - integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w== - -commander@2, commander@^2.11.0, commander@^2.15.1, commander@^2.19.0, commander@^2.20.0: +commander@2, commander@^2.15.1, commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" - integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== +commander@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== -commander@^6.0.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" - integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== +common-tags@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" + integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= compare-angle@^1.0.0: version "1.0.1" @@ -2269,6 +3851,33 @@ component-emitter@^1.2.1, component-emitter@~1.3.0: resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== +compose-function@3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/compose-function/-/compose-function-3.0.3.tgz#9ed675f13cc54501d30950a486ff6a7ba3ab185f" + integrity sha1-ntZ18TzFRQHTCVCkhv9qe6OrGF8= + dependencies: + arity-n "^1.0.4" + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + compute-dims@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/compute-dims/-/compute-dims-1.1.0.tgz#6d5b712929b6c531af3b4d580ed5adacbbd77e0c" @@ -2285,7 +3894,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.2, concat-stream@~1.6.0: +concat-stream@^1.5.0, concat-stream@^1.5.2: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -2305,6 +3914,16 @@ concat-stream@~2.0.0: readable-stream "^3.0.2" typedarray "^0.0.6" +confusing-browser-globals@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz#30d1e7f3d1b882b25ec4933d1d1adac353d20a59" + integrity sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA== + +connect-history-api-fallback@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" + integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== + console-browserify@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" @@ -2325,13 +3944,35 @@ constants-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= -convert-source-map@^1.5.1, convert-source-map@^1.7.0: +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= + +content-disposition@0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== + dependencies: + safe-buffer "5.1.2" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +convert-source-map@1.7.0, convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== dependencies: safe-buffer "~5.1.1" +convert-source-map@^0.3.3: + version "0.3.5" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-0.3.5.tgz#f1d802950af7dd2631a1febe0596550c86ab3190" + integrity sha1-8dgClQr33SYxof6+BZZVDIarMZA= + convex-hull@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/convex-hull/-/convex-hull-1.0.3.tgz#20a3aa6ce87f4adea2ff7d17971c9fc1c67e1fff" @@ -2341,23 +3982,55 @@ convex-hull@^1.0.3: incremental-convex-hull "^1.0.1" monotone-convex-hull-2d "^1.0.1" +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + +cookie@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" + integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== + +copy-concurrently@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" + integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== + dependencies: + aproba "^1.1.1" + fs-write-stream-atomic "^1.0.8" + iferr "^0.1.5" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.0" + copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= -core-js-compat@^3.8.0: - version "3.8.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.8.1.tgz#8d1ddd341d660ba6194cbe0ce60f4c794c87a36e" - integrity sha512-a16TLmy9NVD1rkjUGbwuyWkiDoN0FDpAwrfLONvHFQx0D9k7J9y0srwMT8QP/Z6HE3MIFaVynEeYwZwPX1o5RQ== +core-js-compat@^3.6.2, core-js-compat@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.7.0.tgz#8479c5d3d672d83f1f5ab94cf353e57113e065ed" + integrity sha512-V8yBI3+ZLDVomoWICO6kq/CD28Y4r1M7CWeO4AGpMdMfseu8bkSubBmUPySMGKRTS+su4XQ07zUkAsiu9FCWTg== dependencies: - browserslist "^4.15.0" + browserslist "^4.14.6" semver "7.0.0" -core-js@^2.4.0, core-js@^2.6.5: - version "2.6.12" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" - integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== +core-js-pure@^3.0.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.7.0.tgz#28a57c861d5698e053f0ff36905f7a3301b4191e" + integrity sha512-EZD2ckZysv8MMt4J6HSvS9K2GdtlZtdBncKAmF9lr2n0c9dJUaUN88PSTjvgwCgQPWKTkERXITgS6JJRAnljtg== + +core-js@^2.4.0: + version "2.6.11" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" + integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== + +core-js@^3.6.5: + version "3.7.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.7.0.tgz#b0a761a02488577afbf97179e4681bf49568520f" + integrity sha512-NwS7fI5M5B85EwpWuIwJN4i/fbisQUwLwiSNUWeXlkAZ0sbBjLEvLvFLf1uzAUV66PcEPt4xCGCmOZSxVf3xzA== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -2374,6 +4047,17 @@ cosmiconfig@^5.0.0: js-yaml "^3.13.1" parse-json "^4.0.0" +cosmiconfig@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" + integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.7.2" + cosmiconfig@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" @@ -2421,7 +4105,16 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" -cross-spawn@^6.0.4: +cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +cross-spawn@^6.0.0: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== @@ -2449,6 +4142,18 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-random-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" + integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= + +css-blank-pseudo@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5" + integrity sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w== + dependencies: + postcss "^7.0.5" + css-color-names@0.0.4, css-color-names@^0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" @@ -2502,23 +4207,54 @@ css-global-keywords@^1.0.1: resolved "https://registry.yarnpkg.com/css-global-keywords/-/css-global-keywords-1.0.1.tgz#72a9aea72796d019b1d2a3252de4e5aaa37e4a69" integrity sha1-cqmupyeW0Bmx0qMlLeTlqqN+Smk= -css-modules-loader-core@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/css-modules-loader-core/-/css-modules-loader-core-1.1.0.tgz#5908668294a1becd261ae0a4ce21b0b551f21d16" - integrity sha1-WQhmgpShvs0mGuCkziGwtVHyHRY= +css-has-pseudo@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz#3c642ab34ca242c59c41a125df9105841f6966ee" + integrity sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ== dependencies: - icss-replace-symbols "1.1.0" - postcss "6.0.1" - postcss-modules-extract-imports "1.1.0" - postcss-modules-local-by-default "1.2.0" - postcss-modules-scope "1.1.0" - postcss-modules-values "1.3.0" + postcss "^7.0.6" + postcss-selector-parser "^5.0.0-rc.4" + +css-loader@4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.3.0.tgz#c888af64b2a5b2e85462c72c0f4a85c7e2e0821e" + integrity sha512-rdezjCjScIrsL8BSYszgT4s476IcNKt6yX69t0pHjJVnPUTDpn4WfIpDQTN3wCJvUvfsz/mFjuGOekf3PY3NUg== + dependencies: + camelcase "^6.0.0" + cssesc "^3.0.0" + icss-utils "^4.1.1" + loader-utils "^2.0.0" + postcss "^7.0.32" + postcss-modules-extract-imports "^2.0.0" + postcss-modules-local-by-default "^3.0.3" + postcss-modules-scope "^2.2.0" + postcss-modules-values "^3.0.0" + postcss-value-parser "^4.1.0" + schema-utils "^2.7.1" + semver "^7.3.2" + +css-prefers-color-scheme@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz#6f830a2714199d4f0d0d0bb8a27916ed65cff1f4" + integrity sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg== + dependencies: + postcss "^7.0.5" css-select-base-adapter@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== +css-select@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" + integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= + dependencies: + boolbase "~1.0.0" + css-what "2.1" + domutils "1.5.1" + nth-check "~1.0.1" + css-select@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" @@ -2529,14 +4265,6 @@ css-select@^2.0.0: domutils "^1.7.0" nth-check "^1.0.2" -css-selector-tokenizer@^0.7.0: - version "0.7.3" - resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz#735f26186e67c749aaf275783405cf0661fae8f1" - integrity sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg== - dependencies: - cssesc "^3.0.0" - fastparse "^1.1.2" - css-system-font-keywords@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/css-system-font-keywords/-/css-system-font-keywords-1.0.0.tgz#85c6f086aba4eb32c571a3086affc434b84823ed" @@ -2550,24 +4278,39 @@ css-tree@1.0.0-alpha.37: mdn-data "2.0.4" source-map "^0.6.1" -css-tree@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.2.tgz#9ae393b5dafd7dae8a622475caec78d3d8fbd7b5" - integrity sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ== +css-tree@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.1.tgz#30b8c0161d9fb4e9e2141d762589b6ec2faebd2e" + integrity sha512-NVN42M2fjszcUNpDbdkvutgQSlFYsr1z7kqeuCagHnNLBfYor6uP1WL1KrkmdYZ5Y1vTBCIOI/C/+8T98fJ71w== dependencies: mdn-data "2.0.14" source-map "^0.6.1" -css-unit-converter@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.2.tgz#4c77f5a1954e6dbff60695ecb214e3270436ab21" - integrity sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA== +css-what@2.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" + integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== css-what@^3.2.1: version "3.4.2" resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= + +css@^2.0.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" + integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== + dependencies: + inherits "^2.0.3" + source-map "^0.6.1" + source-map-resolve "^0.5.2" + urix "^0.1.0" + css@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" @@ -2582,6 +4325,16 @@ csscolorparser@~1.0.3: resolved "https://registry.yarnpkg.com/csscolorparser/-/csscolorparser-1.0.3.tgz#b34f391eea4da8f3e98231e2ccd8df9c041f171b" integrity sha1-s085HupNqPPpgjHizNjfnAQfFxs= +cssdb@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0" + integrity sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ== + +cssesc@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703" + integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg== + cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" @@ -2645,7 +4398,7 @@ cssnano-util-same-parent@^4.0.0: resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== -cssnano@^4.0.0, cssnano@^4.1.10: +cssnano@^4.1.10: version "4.1.10" resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== @@ -2656,23 +4409,28 @@ cssnano@^4.0.0, cssnano@^4.1.10: postcss "^7.0.0" csso@^4.0.2: - version "4.2.0" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== + version "4.1.1" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.1.1.tgz#e0cb02d6eb3af1df719222048e4359efd662af13" + integrity sha512-Rvq+e1e0TFB8E8X+8MQjHSY6vtol45s5gxtLI/018UsAn2IBMmwNEZRM/h+HVnAJRHjasLIKKUO3uvoMM28LvA== dependencies: - css-tree "^1.1.2" + css-tree "^1.0.0" -cssom@0.3.x, cssom@^0.3.4: +cssom@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== + +cssom@~0.3.6: version "0.3.8" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== -cssstyle@^1.1.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.4.0.tgz#9d31328229d3c565c61e586b02041a28fccdccf1" - integrity sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA== +cssstyle@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== dependencies: - cssom "0.3.x" + cssom "~0.3.6" csstype@^3.0.2: version "3.0.5" @@ -2691,6 +4449,11 @@ cwise-compiler@^1.0.0, cwise-compiler@^1.1.2: dependencies: uniq "^1.0.0" +cyclist@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" + integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= + d3-array@1, d3-array@^1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f" @@ -2780,6 +4543,11 @@ d@1, d@^1.0.1: es5-ext "^0.10.50" type "^1.0.1" +damerau-levenshtein@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791" + integrity sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug== + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -2787,31 +4555,30 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -data-urls@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" - integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ== +data-urls@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" + integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== dependencies: - abab "^2.0.0" - whatwg-mimetype "^2.2.0" - whatwg-url "^7.0.0" + abab "^2.0.3" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" -deasync@^0.1.14: - version "0.1.21" - resolved "https://registry.yarnpkg.com/deasync/-/deasync-0.1.21.tgz#bb11eabd4466c0d8776f0d82deb8a6126460d30f" - integrity sha512-kUmM8Y+PZpMpQ+B4AuOW9k2Pfx/mSupJtxOsLzmnHY2WqZUYRFccFn2RhzPAqt3Xb+sorK/badW2D4zNzqZz5w== - dependencies: - bindings "^1.5.0" - node-addon-api "^1.7.1" - -debug@2.6.9, debug@^2.2.0, debug@^2.3.3: +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@^4.1.0: +debug@^3.1.1, debug@^3.2.5: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== @@ -2825,24 +4592,57 @@ debug@~4.1.0: dependencies: ms "^2.1.1" +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +decimal.js@^10.2.0: + version "10.2.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3" + integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw== + decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= -deep-is@~0.1.3: +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= + +deep-equal@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" + integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== + dependencies: + is-arguments "^1.0.4" + is-date-object "^1.0.1" + is-regex "^1.0.4" + object-is "^1.0.1" + object-keys "^1.1.1" + regexp.prototype.flags "^1.2.0" + +deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -defaults@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" - integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= - dependencies: - clone "^1.0.2" +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== -define-properties@^1.1.3: +default-gateway@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" + integrity sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA== + dependencies: + execa "^1.0.0" + ip-regex "^2.1.0" + +define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -2876,6 +4676,19 @@ defined@^1.0.0: resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= +del@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" + integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ== + dependencies: + "@types/glob" "^7.1.1" + globby "^6.1.0" + is-path-cwd "^2.0.0" + is-path-in-cwd "^2.0.0" + p-map "^2.0.0" + pify "^4.0.1" + rimraf "^2.6.3" + delaunay-triangulate@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/delaunay-triangulate/-/delaunay-triangulate-1.1.6.tgz#5bbca21b078198d4bc3c75796a35cbb98c25954c" @@ -2894,11 +4707,6 @@ depd@~1.1.2: resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= -dependency-graph@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.9.0.tgz#11aed7e203bc8b00f48356d92db27b265c445318" - integrity sha512-9YLIBURXj4DJMFALxXw9K3Y3rwb5Fk0X5/8ipCzaN84+gKxoHK43tVKRNakCQbiEx07E8Uwhuq21BpUagFhZ8w== - des.js@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" @@ -2917,19 +4725,28 @@ detect-kerning@^2.1.2: resolved "https://registry.yarnpkg.com/detect-kerning/-/detect-kerning-2.1.2.tgz#4ecd548e4a5a3fc880fe2a50609312d000fa9fc2" integrity sha512-I3JIbrnKPAntNLl1I6TpSQQdQ4AutYzv/sKMFKbepawV/hlH0GmYKhUoOEMd4xqaUHT+Bm0f4127lh5qs1m1tw== -detective@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" - integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== - dependencies: - acorn-node "^1.6.1" - defined "^1.0.0" - minimist "^1.1.1" +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -didyoumean@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.1.tgz#e92edfdada6537d484d73c0172fd1eba0c4976ff" - integrity sha1-6S7f2tplN9SE1zwBcv0eugxJdv8= +detect-node@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" + integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== + +detect-port-alt@1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" + integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q== + dependencies: + address "^1.0.1" + debug "^2.6.0" + +diff-sequences@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" + integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== diffie-hellman@^5.0.0: version "5.0.3" @@ -2947,6 +4764,60 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= + +dns-packet@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" + integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== + dependencies: + ip "^1.1.0" + safe-buffer "^5.0.1" + +dns-txt@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" + integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY= + dependencies: + buffer-indexof "^1.0.0" + +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-accessibility-api@^0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166" + integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ== + +dom-converter@^0.2: + version "0.2.0" + resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" + integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== + dependencies: + utila "~0.4" + dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -2966,16 +4837,16 @@ domelementtype@1, domelementtype@^1.3.1: integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== domelementtype@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e" - integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w== + version "2.0.2" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.2.tgz#f3b6e549201e46f588b59463dd77187131fe6971" + integrity sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA== -domexception@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" - integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug== +domexception@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" + integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== dependencies: - webidl-conversions "^4.0.2" + webidl-conversions "^5.0.0" domhandler@^2.3.0: version "2.4.2" @@ -2984,6 +4855,14 @@ domhandler@^2.3.0: dependencies: domelementtype "1" +domutils@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" + integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= + dependencies: + dom-serializer "0" + domelementtype "1" + domutils@^1.5.1, domutils@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" @@ -2992,6 +4871,14 @@ domutils@^1.5.1, domutils@^1.7.0: dom-serializer "0" domelementtype "1" +dot-case@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.3.tgz#21d3b52efaaba2ea5fda875bb1aa8124521cf4aa" + integrity sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA== + dependencies: + no-case "^3.0.3" + tslib "^1.10.0" + dot-prop@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" @@ -2999,15 +4886,15 @@ dot-prop@^5.2.0: dependencies: is-obj "^2.0.0" -dotenv-expand@^5.1.0: +dotenv-expand@5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== -dotenv@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef" - integrity sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow== +dotenv@8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" + integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== double-bits@^1.1.0, double-bits@^1.1.1: version "1.1.1" @@ -3032,14 +4919,12 @@ dup@^1.0.0: resolved "https://registry.yarnpkg.com/dup/-/dup-1.0.0.tgz#51fc5ac685f8196469df0b905e934b20af5b4029" integrity sha1-UfxaxoX4GWRp3wuQXpNLIK9bQCk= -duplexer2@~0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= - dependencies: - readable-stream "^2.0.2" +duplexer@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== -duplexify@^3.4.5: +duplexify@^3.4.2, duplexify@^3.4.5, duplexify@^3.6.0: version "3.7.1" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== @@ -3074,10 +4959,15 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.3.621: - version "1.3.625" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.625.tgz#a7bd18da4dc732c180b2e95e0e296c0bf22f3bd6" - integrity sha512-CsLk/r0C9dAzVPa9QF74HIXduxaucsaRfqiOYvIv2PRhvyC6EOqc/KbpgToQuDVgPf3sNAFZi3iBu4vpGOwGag== +ejs@^2.6.1: + version "2.7.4" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba" + integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== + +electron-to-chromium@^1.3.564, electron-to-chromium@^1.3.591: + version "1.3.603" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.603.tgz#1b71bec27fb940eccd79245f6824c63d5f7e8abf" + integrity sha512-J8OHxOeJkoSLgBXfV9BHgKccgfLMHh+CoeRo6wJsi6m0k3otaxS/5vrHpMNSEYY4MISwewqanPOuhAtuE8riQQ== element-size@^1.1.1: version "1.1.1" @@ -3104,17 +4994,42 @@ elliptic@^6.5.3: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.0" +emittery@^0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" + integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ== + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emoji-regex@^9.0.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.0.tgz#a26da8e832b16a9753309f25e35e3c0efb9a066a" + integrity sha512-DNc3KFPK18bPdElMJnf/Pkv5TXhxFU3YFDEuGLDRtPmV4rkmCjBkCSEp22u6rBHdSN9Vlp/GK7k98prmE1Jgug== + +emojis-list@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" + integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -end-of-stream@^1.0.0: +end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -3144,7 +5059,23 @@ engine.io-parser@~4.0.1: dependencies: base64-arraybuffer "0.1.4" -entities@^1.1.1, entities@^1.1.2: +enhanced-resolve@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz#3b806f3bfafc1ec7de69551ef93cca46c1704126" + integrity sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.5.0" + tapable "^1.0.0" + +enquirer@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + +entities@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== @@ -3154,19 +5085,28 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== -envinfo@^7.3.1: - version "7.7.3" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.7.3.tgz#4b2d8622e3e7366afb8091b23ed95569ea0208cc" - integrity sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA== +errno@^0.1.3, errno@~0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" + integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== + dependencies: + prr "~1.0.1" -error-ex@^1.3.1: +error-ex@^1.2.0, error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.2: +error-stack-parser@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.6.tgz#5a99a707bd7a4c58a797902d48d82803ede6aad8" + integrity sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ== + dependencies: + stackframe "^1.1.1" + +es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: version "1.17.7" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== @@ -3183,7 +5123,7 @@ es-abstract@^1.17.2: string.prototype.trimend "^1.0.1" string.prototype.trimstart "^1.0.1" -es-abstract@^1.18.0-next.1: +es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1: version "1.18.0-next.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== @@ -3219,7 +5159,7 @@ es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50: es6-symbol "~3.1.3" next-tick "~1.0.0" -es6-iterator@^2.0.3, es6-iterator@~2.0.3: +es6-iterator@2.0.3, es6-iterator@^2.0.3, es6-iterator@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= @@ -3251,7 +5191,7 @@ es6-weak-map@^2.0.3: es6-iterator "^2.0.3" es6-symbol "^3.1.1" -escalade@^3.1.1: +escalade@^3.0.2, escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== @@ -3261,12 +5201,17 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@2.0.0, escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@^1.11.0, escodegen@^1.11.1: +escodegen@^1.11.1, escodegen@^1.14.1: version "1.14.3" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== @@ -3278,33 +5223,244 @@ escodegen@^1.11.0, escodegen@^1.11.1: optionalDependencies: source-map "~0.6.1" -escodegen@~1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.1.tgz#dbae17ef96c8e4bedb1356f4504fa4cc2f7cb7e2" - integrity sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q== +eslint-config-react-app@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz#ccff9fc8e36b322902844cbd79197982be355a0e" + integrity sha512-bpoAAC+YRfzq0dsTk+6v9aHm/uqnDwayNAXleMypGl6CpxI9oXXscVHo4fk3eJPIn+rsbtNetB4r/ZIidFIE8A== dependencies: - esprima "^3.1.3" - estraverse "^4.2.0" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.6.1" + confusing-browser-globals "^1.0.10" -esprima@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" - integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= +eslint-import-resolver-node@^0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" + integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== + dependencies: + debug "^2.6.9" + resolve "^1.13.1" + +eslint-module-utils@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" + integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== + dependencies: + debug "^2.6.9" + pkg-dir "^2.0.0" + +eslint-plugin-flowtype@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-5.2.0.tgz#a4bef5dc18f9b2bdb41569a4ab05d73805a3d261" + integrity sha512-z7ULdTxuhlRJcEe1MVljePXricuPOrsWfScRXFhNzVD5dmTHWjIF57AxD0e7AbEoLSbjSsaA5S+hCg43WvpXJQ== + dependencies: + lodash "^4.17.15" + string-natural-compare "^3.0.1" + +eslint-plugin-import@^2.22.1: + version "2.22.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz#0896c7e6a0cf44109a2d97b95903c2bb689d7702" + integrity sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw== + dependencies: + array-includes "^3.1.1" + array.prototype.flat "^1.2.3" + contains-path "^0.1.0" + debug "^2.6.9" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.4" + eslint-module-utils "^2.6.0" + has "^1.0.3" + minimatch "^3.0.4" + object.values "^1.1.1" + read-pkg-up "^2.0.0" + resolve "^1.17.0" + tsconfig-paths "^3.9.0" + +eslint-plugin-jest@^24.1.0: + version "24.1.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.1.3.tgz#fa3db864f06c5623ff43485ca6c0e8fc5fe8ba0c" + integrity sha512-dNGGjzuEzCE3d5EPZQ/QGtmlMotqnYWD/QpCZ1UuZlrMAdhG5rldh0N0haCvhGnUkSeuORS5VNROwF9Hrgn3Lg== + dependencies: + "@typescript-eslint/experimental-utils" "^4.0.1" + +eslint-plugin-jsx-a11y@^6.3.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz#a2d84caa49756942f42f1ffab9002436391718fd" + integrity sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg== + dependencies: + "@babel/runtime" "^7.11.2" + aria-query "^4.2.2" + array-includes "^3.1.1" + ast-types-flow "^0.0.7" + axe-core "^4.0.2" + axobject-query "^2.2.0" + damerau-levenshtein "^1.0.6" + emoji-regex "^9.0.0" + has "^1.0.3" + jsx-ast-utils "^3.1.0" + language-tags "^1.0.5" + +eslint-plugin-react-hooks@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz#8c229c268d468956334c943bb45fc860280f5556" + integrity sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ== + +eslint-plugin-react@^7.21.5: + version "7.21.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.21.5.tgz#50b21a412b9574bfe05b21db176e8b7b3b15bff3" + integrity sha512-8MaEggC2et0wSF6bUeywF7qQ46ER81irOdWS4QWxnnlAEsnzeBevk1sWh7fhpCghPpXb+8Ks7hvaft6L/xsR6g== + dependencies: + array-includes "^3.1.1" + array.prototype.flatmap "^1.2.3" + doctrine "^2.1.0" + has "^1.0.3" + jsx-ast-utils "^2.4.1 || ^3.0.0" + object.entries "^1.1.2" + object.fromentries "^2.0.2" + object.values "^1.1.1" + prop-types "^15.7.2" + resolve "^1.18.1" + string.prototype.matchall "^4.0.2" + +eslint-plugin-testing-library@^3.9.2: + version "3.10.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-testing-library/-/eslint-plugin-testing-library-3.10.1.tgz#4dd02306d601c3238fdabf1d1dbc5f2a8e85d531" + integrity sha512-nQIFe2muIFv2oR2zIuXE4vTbcFNx8hZKRzgHZqJg8rfopIWwoTwtlbCCNELT/jXzVe1uZF68ALGYoDXjLczKiQ== + dependencies: + "@typescript-eslint/experimental-utils" "^3.10.1" + +eslint-scope@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" + integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-scope@^5.0.0, eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^2.0.0, eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint-visitor-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" + integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== + +eslint-webpack-plugin@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/eslint-webpack-plugin/-/eslint-webpack-plugin-2.4.0.tgz#dcfd2653d0e15e52251f34dd3690ce60718d5589" + integrity sha512-j0lAJj3RnStAFdIH2P0+nsEImiBijwogZhL1go4bI6DE+9OhQuOmJ/xtmxkLtNr1w0cf5SRNkDlmIe8t/pHgww== + dependencies: + "@types/eslint" "^7.2.4" + arrify "^2.0.1" + jest-worker "^26.6.2" + micromatch "^4.0.2" + schema-utils "^3.0.0" + +eslint@^7.11.0: + version "7.14.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.14.0.tgz#2d2cac1d28174c510a97b377f122a5507958e344" + integrity sha512-5YubdnPXrlrYAFCKybPuHIAH++PINe1pmKNc5wQRB9HSbqIK1ywAnntE3Wwua4giKu0bjligf1gLF6qxMGOYRA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@eslint/eslintrc" "^0.2.1" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + enquirer "^2.3.5" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.0" + esquery "^1.2.0" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash "^4.17.19" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^7.3.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.0.tgz#dc30437cf67947cf576121ebd780f15eeac72348" + integrity sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.3.0" esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -estraverse@^4.2.0: +esquery@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" + integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.1.0, esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1, estraverse@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" + integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== + +estree-walker@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" + integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== + +estree-walker@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" + integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -3315,11 +5471,23 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + events@^3.0.0, events@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== +eventsource@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" + integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ== + dependencies: + original "^1.0.0" + evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -3328,6 +5496,44 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: md5.js "^1.3.4" safe-buffer "^5.1.1" +exec-sh@^0.3.2: + version "0.3.4" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" + integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A== + +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +execa@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" + integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + expand-brackets@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" @@ -3341,6 +5547,54 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" +expect@^26.6.0, expect@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" + integrity sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA== + dependencies: + "@jest/types" "^26.6.2" + ansi-styles "^4.0.0" + jest-get-type "^26.3.0" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-regex-util "^26.0.0" + +express@^4.17.1: + version "4.17.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" + integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== + dependencies: + accepts "~1.3.7" + array-flatten "1.1.1" + body-parser "1.19.0" + content-disposition "0.5.3" + content-type "~1.0.4" + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "~1.1.2" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.5" + qs "6.7.0" + range-parser "~1.2.1" + safe-buffer "5.1.2" + send "0.17.1" + serve-static "1.14.1" + setprototypeof "1.1.1" + statuses "~1.5.0" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + ext@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" @@ -3412,18 +5666,6 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^2.2.2: - version "2.2.7" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" - integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw== - dependencies: - "@mrmlnc/readdir-enhanced" "^2.2.1" - "@nodelib/fs.stat" "^1.1.2" - glob-parent "^3.1.0" - is-glob "^4.0.0" - merge2 "^1.2.3" - micromatch "^3.1.10" - fast-glob@^3.1.1: version "3.2.4" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" @@ -3443,21 +5685,16 @@ fast-isnumeric@^1.1.4: dependencies: is-string-blank "^1.0.1" -fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@~2.0.6: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -fastparse@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" - integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== - fastq@^1.6.0: version "1.9.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.9.0.tgz#e16a72f338eaca48e91b5c23593bcc2ef66b7947" @@ -3465,15 +5702,56 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +faye-websocket@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" + integrity sha1-TkkvjQTftviQA1B/btvy1QHnxvQ= + dependencies: + websocket-driver ">=0.5.1" + +faye-websocket@~0.11.1: + version "0.11.3" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" + integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA== + dependencies: + websocket-driver ">=0.5.1" + +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + +figgy-pudding@^3.5.1: + version "3.5.2" + resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" + integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +file-loader@6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.1.1.tgz#a6f29dfb3f5933a1c350b2dbaa20ac5be0539baa" + integrity sha512-Klt8C4BjWSXYQAfhpYYkG4qHNTna4toMHEbWrI5IuVoxbU6uiDKeKAP99R8mmbJi3lvewn/jQBOgU4+NS3tDQw== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + file-uri-to-path@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== -filesize@^3.6.0: - version "3.6.1" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317" - integrity sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg== +filesize@6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-6.1.0.tgz#e81bdaa780e2451d714d71c0d7a4f3238d37ad00" + integrity sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg== fill-range@^4.0.0: version "4.0.0" @@ -3500,6 +5778,73 @@ filtered-vector@^1.2.1: binary-search-bounds "^1.0.0" cubic-hermite "^1.0.0" +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +find-cache-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" + integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== + dependencies: + commondir "^1.0.1" + make-dir "^2.0.0" + pkg-dir "^3.0.0" + +find-cache-dir@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" + integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-up@4.1.0, find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^2.0.0, find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== + flatten-vertex-data@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flatten-vertex-data/-/flatten-vertex-data-1.0.2.tgz#889fd60bea506006ca33955ee1105175fb620219" @@ -3507,11 +5852,29 @@ flatten-vertex-data@^1.0.2: dependencies: dtype "^2.0.0" +flatten@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" + integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== + flip-pixels@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flip-pixels/-/flip-pixels-1.0.2.tgz#aad7b7d9fc65932d5f27e2e4dac4b494140845e4" integrity sha512-oXbJGbjDnfJRWPC7Va38EFhd+A8JWE5/hCiKcK8qjCdbLj9DTpsq6MEudwpRTH+V4qq+Jw7d3pUgQdSr3x3mTA== +flush-write-stream@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" + integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== + dependencies: + inherits "^2.0.3" + readable-stream "^2.3.6" + +follow-redirects@^1.0.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" + integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== + font-atlas@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/font-atlas/-/font-atlas-2.1.0.tgz#aa2d6dcf656a6c871d66abbd3dfbea2f77178348" @@ -3541,6 +5904,19 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +fork-ts-checker-webpack-plugin@4.1.6: + version "4.1.6" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-4.1.6.tgz#5055c703febcf37fa06405d400c122b905167fc5" + integrity sha512-DUxuQaKoqfNne8iikd14SAkh5uw4+8vNifp6gmA73yYNS6ywLIWSLD/n/mBzHQRpW3J7rbATEakmiA8JvkTyZw== + dependencies: + "@babel/code-frame" "^7.5.5" + chalk "^2.4.1" + micromatch "^3.1.10" + minimatch "^3.0.4" + semver "^5.6.0" + tapable "^1.0.0" + worker-rpc "^0.1.0" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -3550,10 +5926,10 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -fraction.js@^4.0.12: - version "4.0.12" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.12.tgz#0526d47c65a5fb4854df78bc77f7bec708d7b8c3" - integrity sha512-8Z1K0VTG4hzYY7kA/1sj4/r1/RWLBD3xwReT/RCrUCbzPszjNQCCsy3ktkU/eaEqX3MYa4pY37a52eiBlPMlhA== +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= fragment-cache@^0.2.1: version "0.2.1" @@ -3567,7 +5943,7 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= -from2@^2.3.0: +from2@^2.1.0, from2@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= @@ -3575,7 +5951,25 @@ from2@^2.3.0: inherits "^2.0.1" readable-stream "^2.0.0" -fs-extra@^9.0.0, fs-extra@^9.0.1: +fs-extra@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== @@ -3585,6 +5979,23 @@ fs-extra@^9.0.0, fs-extra@^9.0.1: jsonfile "^6.0.1" universalify "^1.0.0" +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs-write-stream-atomic@^1.0.8: + version "1.0.10" + resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" + integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= + dependencies: + graceful-fs "^4.1.2" + iferr "^0.1.5" + imurmurhash "^0.1.4" + readable-stream "1 || 2" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -3598,6 +6009,11 @@ fsevents@^1.2.7: bindings "^1.5.0" nan "^2.12.1" +fsevents@^2.1.2, fsevents@^2.1.3: + version "2.2.1" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.2.1.tgz#1fb02ded2036a8ac288d507a65962bd87b97628d" + integrity sha512-bTLYHSeC0UH/EFXS9KqWnXuOl/wHK5Z/d+ghd5AsFMYN7wIGkUCOJyzy88+wJKkZPGON8u4Z9f6U4FdgURE9qA== + fsevents@~2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" @@ -3608,7 +6024,7 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -functional-red-black-tree@^1.0.0: +functional-red-black-tree@^1.0.0, functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= @@ -3628,7 +6044,7 @@ geojson-vt@^3.2.1: resolved "https://registry.yarnpkg.com/geojson-vt/-/geojson-vt-3.2.1.tgz#f8adb614d2c1d3f6ee7c4265cad4bbf3ad60c8b7" integrity sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg== -get-caller-file@^2.0.5: +get-caller-file@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -3647,15 +6063,29 @@ get-intrinsic@^1.0.0: has "^1.0.3" has-symbols "^1.0.1" -get-port@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" - integrity sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw= +get-own-enumerable-property-symbols@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" + integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== -get-stdin@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" - integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-stream@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" @@ -4049,19 +6479,14 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.1.0, glob-parent@~5.1.0: +glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: version "5.1.1" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== dependencies: is-glob "^4.0.1" -glob-to-regexp@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" - integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= - -glob@^7.0.0, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: +glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -4073,12 +6498,35 @@ glob@^7.0.0, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" +global-modules@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globby@^11.0.0: +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + +globby@11.0.1, globby@^11.0.1: version "11.0.1" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== @@ -4090,6 +6538,17 @@ globby@^11.0.0: merge2 "^1.3.0" slash "^3.0.0" +globby@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= + dependencies: + array-union "^1.0.1" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + glsl-inject-defines@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/glsl-inject-defines/-/glsl-inject-defines-1.0.3.tgz#dd1aacc2c17fcb2bd3fc32411c6633d0d7b60fd4" @@ -4247,24 +6706,34 @@ glslify@^7.0.0, glslify@^7.1.1: through2 "^2.0.1" xtend "^4.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== -grapheme-breaker@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/grapheme-breaker/-/grapheme-breaker-0.3.2.tgz#5b9e6b78c3832452d2ba2bb1cb830f96276410ac" - integrity sha1-W55reMODJFLSuiuxy4MPlidkEKw= - dependencies: - brfs "^1.2.0" - unicode-trie "^0.3.1" - grid-index@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/grid-index/-/grid-index-1.1.0.tgz#97f8221edec1026c8377b86446a7c71e79522ea7" integrity sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA== +growly@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= + +gzip-size@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" + integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA== + dependencies: + duplexer "^0.1.1" + pify "^4.0.1" + +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -4278,23 +6747,16 @@ har-validator@~5.1.3: ajv "^6.12.3" har-schema "^2.0.0" -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" +harmony-reflect@^1.4.6: + version "1.6.1" + resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.1.tgz#c108d4f2bb451efef7a37861fdbdae72c9bdefa9" + integrity sha512-WJTeyp0JzGtHcuMsi7rw2VwtkvLa+JyfEKJCFyfcS0+CDkjQ5lHPu7zEhFZP+PDSRrEgXa5Ah0l1MbgbE41XjA== has-cors@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk= -has-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" - integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= - has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -4355,7 +6817,7 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" -has@^1.0.0, has@^1.0.1, has@^1.0.3: +has@^1.0.0, has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== @@ -4379,6 +6841,11 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + hex-color-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" @@ -4393,6 +6860,26 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hoopy@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d" + integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ== + +hosted-git-info@^2.1.4: + version "2.8.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" + integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + hsl-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" @@ -4413,40 +6900,52 @@ html-comment-regex@^1.1.0: resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== -html-encoding-sniffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" - integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw== +html-encoding-sniffer@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" + integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== dependencies: - whatwg-encoding "^1.0.1" + whatwg-encoding "^1.0.5" -html-tags@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-1.2.0.tgz#c78de65b5663aa597989dd2b7ab49200d7e4db98" - integrity sha1-x43mW1Zjqll5id0rerSSANfk25g= +html-entities@^1.2.1, html-entities@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44" + integrity sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA== -html-tags@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140" - integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg== +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -htmlnano@^0.2.2: - version "0.2.8" - resolved "https://registry.yarnpkg.com/htmlnano/-/htmlnano-0.2.8.tgz#d9c22daa18c8ea7675a0bf07cc904793ccaeb56f" - integrity sha512-q5gbo4SIDAE5sfJ5V0UD6uu+n1dcO/Mpr0B6SlDlJBoV7xKPne4uG4UwrT8vUWjdjIPJl95TY8EDuEbBW2TG0A== +html-minifier-terser@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#922e96f1f3bb60832c2634b79884096389b1f054" + integrity sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg== dependencies: - cssnano "^4.1.10" - posthtml "^0.13.4" - posthtml-render "^1.3.0" - purgecss "^2.3.0" + camel-case "^4.1.1" + clean-css "^4.2.3" + commander "^4.1.1" + he "^1.2.0" + param-case "^3.0.3" relateurl "^0.2.7" - srcset "^3.0.0" - svgo "^1.3.2" - terser "^4.8.0" - timsort "^0.3.0" - uncss "^0.17.3" + terser "^4.6.3" -htmlparser2@^3.9.2: +html-webpack-plugin@4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz#625097650886b97ea5dae331c320e3238f6c121c" + integrity sha512-MouoXEYSjTzCrjIxWwg8gxL5fE2X2WZJLmBYXlaJhQUH5K/b5OrqmV7T4dB7iu0xkmJ6JlUuV6fFVtnqbPopZw== + dependencies: + "@types/html-minifier-terser" "^5.0.0" + "@types/tapable" "^1.0.5" + "@types/webpack" "^4.41.8" + html-minifier-terser "^5.0.1" + loader-utils "^1.2.3" + lodash "^4.17.15" + pretty-error "^2.1.1" + tapable "^1.1.3" + util.promisify "1.0.0" + +htmlparser2@^3.3.0: version "3.10.1" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== @@ -4458,6 +6957,32 @@ htmlparser2@^3.9.2: inherits "^2.0.1" readable-stream "^3.1.1" +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= + +http-errors@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + http-errors@~1.7.2: version "1.7.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" @@ -4469,6 +6994,30 @@ http-errors@~1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" +http-parser-js@>=0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.2.tgz#da2e31d237b393aae72ace43882dd7e270a8ff77" + integrity sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ== + +http-proxy-middleware@0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" + integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q== + dependencies: + http-proxy "^1.17.0" + is-glob "^4.0.0" + lodash "^4.17.11" + micromatch "^3.1.10" + +http-proxy@^1.17.0: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -4483,6 +7032,11 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -4490,16 +7044,35 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -icss-replace-symbols@1.1.0, icss-replace-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" - integrity sha1-Bupvg2ead0njhs/h/oEq5dsiPe0= +icss-utils@^4.0.0, icss-utils@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" + integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== + dependencies: + postcss "^7.0.14" + +identity-obj-proxy@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14" + integrity sha1-lNK9qWCERT7zb7xarsN+D3nx/BQ= + dependencies: + harmony-reflect "^1.4.6" ieee754@^1.1.12, ieee754@^1.1.4: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== +iferr@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" + integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + ignore@^5.1.4: version "5.1.8" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" @@ -4519,12 +7092,17 @@ image-size@^0.7.5: resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.7.5.tgz#269f357cf5797cb44683dfa99790e54c705ead04" integrity sha512-Hiyv+mXHfFEP7LzUL/llg9RwFxxY+o9N3JVLIeG5E7iFIFAalxvRU9UZthBdYDEVnzHMgjnKJPPpay5BWf1g9g== -import-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-3.0.0.tgz#20845547718015126ea9b3676b7592fb8bd4cf92" - integrity sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg== +immer@7.0.9: + version "7.0.9" + resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.9.tgz#28e7552c21d39dd76feccd2b800b7bc86ee4a62e" + integrity sha512-Vs/gxoM4DqNAYR7pugIxi0Xc8XAun/uy7AQu4fLLqaTBHxjOP9pJ266Q9MWA/ly4z6rAFZbvViOtihxUZ7O28A== + +import-cwd@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" + integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk= dependencies: - import-from "^3.0.0" + import-from "^2.1.0" import-fresh@^2.0.0: version "2.0.0" @@ -4534,7 +7112,7 @@ import-fresh@^2.0.0: caller-path "^2.0.0" resolve-from "^3.0.0" -import-fresh@^3.2.1: +import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: version "3.2.2" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.2.tgz#fc129c160c5d68235507f4331a6baad186bdbc3e" integrity sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw== @@ -4542,12 +7120,33 @@ import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -import-from@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/import-from/-/import-from-3.0.0.tgz#055cfec38cd5a27d8057ca51376d7d3bf0891966" - integrity sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ== +import-from@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" + integrity sha1-M1238qev/VOqpHHUuAId7ja387E= dependencies: - resolve-from "^5.0.0" + resolve-from "^3.0.0" + +import-local@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" + integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== + dependencies: + pkg-dir "^3.0.0" + resolve-cwd "^2.0.0" + +import-local@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" + integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= incremental-convex-hull@^1.0.1: version "1.0.1" @@ -4557,11 +7156,21 @@ incremental-convex-hull@^1.0.1: robust-orientation "^1.1.2" simplicial-complex "^1.0.0" +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= +infer-owner@^1.0.3, infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -4585,6 +7194,28 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= +ini@^1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + +internal-ip@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" + integrity sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg== + dependencies: + default-gateway "^4.2.0" + ipaddr.js "^1.9.0" + +internal-slot@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.2.tgz#9c2e9fb3cd8e5e4256c6f45fe310067fcfa378a3" + integrity sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g== + dependencies: + es-abstract "^1.17.0-next.1" + has "^1.0.3" + side-channel "^1.0.2" + interval-tree-1d@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/interval-tree-1d/-/interval-tree-1d-1.0.3.tgz#8fdbde02b6b2c7dbdead636bcbed8e08710d85c1" @@ -4602,12 +7233,27 @@ iota-array@^1.0.0: resolved "https://registry.yarnpkg.com/iota-array/-/iota-array-1.0.0.tgz#81ef57fe5d05814cd58c2483632a99c30a0e8087" integrity sha1-ge9X/l0FgUzVjCSDYyqZwwoOgIc= +ip-regex@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" + integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= + +ip@^1.1.0, ip@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= + +ipaddr.js@1.9.1, ipaddr.js@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + is-absolute-url@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= -is-absolute-url@^3.0.1: +is-absolute-url@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== @@ -4626,6 +7272,11 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +is-arguments@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" + integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -4680,6 +7331,13 @@ is-callable@^1.1.4, is-callable@^1.2.2: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + is-color-stop@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" @@ -4692,10 +7350,10 @@ is-color-stop@^1.0.0: rgb-regex "^1.0.1" rgba-regex "^1.0.0" -is-core-module@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" - integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== +is-core-module@^2.0.0, is-core-module@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.1.0.tgz#a4cc031d9b1aca63eecbd18a650e13cb4eeab946" + integrity sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA== dependencies: has "^1.0.3" @@ -4741,6 +7399,11 @@ is-directory@^0.3.1: resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= +is-docker@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" + integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== + is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -4773,11 +7436,21 @@ is-float-array@^1.0.0: resolved "https://registry.yarnpkg.com/is-float-array/-/is-float-array-1.0.0.tgz#96d67b1cbadf47ab1e05be208933acd386978a09" integrity sha512-4ew1Sx6B6kEAl3T3NOM0yB94J3NZnBdNt4paw0e8nY73yHHTeTEhyQ3Lj7EQEnv5LD+GxNTaT4L46jcKjjpLiQ== +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" @@ -4792,13 +7465,6 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-html@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-html/-/is-html-1.1.0.tgz#e04f1c18d39485111396f9a0273eab51af218464" - integrity sha1-4E8cGNOUhRETlvmgJz6rUa8hhGQ= - dependencies: - html-tags "^1.0.0" - is-iexplorer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-iexplorer/-/is-iexplorer-1.0.0.tgz#1d72bc66d3fe22eaf6170dda8cf10943248cfc76" @@ -4809,10 +7475,15 @@ is-mobile@^2.2.1, is-mobile@^2.2.2: resolved "https://registry.yarnpkg.com/is-mobile/-/is-mobile-2.2.2.tgz#f6c9c5d50ee01254ce05e739bdd835f1ed4e9954" integrity sha512-wW/SXnYJkTjs++tVK5b6kVITZpAZPtUrt9SF80vvxGiF/Oywal+COk1jlRkiVq15RFNEQKQY31TkV24/1T5cVg== +is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" + integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= + is-negative-zero@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" - integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" + integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= is-number@^3.0.0: version "3.0.0" @@ -4836,7 +7507,26 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-plain-obj@^1.1.0: +is-path-cwd@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== + +is-path-in-cwd@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb" + integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ== + dependencies: + is-path-inside "^2.1.0" + +is-path-inside@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" + integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg== + dependencies: + path-is-inside "^1.0.2" + +is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= @@ -4848,23 +7538,53 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-regex@^1.1.1: +is-potential-custom-element-name@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397" + integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c= + +is-regex@^1.0.4, is-regex@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== dependencies: has-symbols "^1.0.1" +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= + is-resolvable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== +is-root@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" + integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + +is-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + is-string-blank@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-string-blank/-/is-string-blank-1.0.1.tgz#866dca066d41d2894ebdfd2d8fe93e586e583a03" integrity sha512-9H+ZBCVs3L9OYqv8nuUAzpcT9OTgMD1yAWrG7ihlnibdkbtB850heAmYWxHuXc4CHy4lKeK69tN+ny1K7gBIrw== +is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + is-svg-path@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-svg-path/-/is-svg-path-1.0.2.tgz#77ab590c12b3d20348e5c7a13d0040c87784dda0" @@ -4884,16 +7604,11 @@ is-symbol@^1.0.2: dependencies: has-symbols "^1.0.1" -is-typedarray@~1.0.0: +is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -is-url@^1.2.2: - version "1.2.4" - resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" - integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== - is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -4904,6 +7619,13 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= +is-wsl@^2.1.1, is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -4941,15 +7663,491 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= +istanbul-lib-coverage@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" + integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== + +istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" + integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== + dependencies: + "@babel/core" "^7.7.5" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9" + integrity sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b" + integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" + integrity sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ== + dependencies: + "@jest/types" "^26.6.2" + execa "^4.0.0" + throat "^5.0.0" + +jest-circus@26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-26.6.0.tgz#7d9647b2e7f921181869faae1f90a2629fd70705" + integrity sha512-L2/Y9szN6FJPWFK8kzWXwfp+FOR7xq0cUL4lIsdbIdwz3Vh6P1nrpcqOleSzr28zOtSHQNV9Z7Tl+KkuK7t5Ng== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^26.6.0" + "@jest/test-result" "^26.6.0" + "@jest/types" "^26.6.0" + "@types/babel__traverse" "^7.0.4" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + expect "^26.6.0" + is-generator-fn "^2.0.0" + jest-each "^26.6.0" + jest-matcher-utils "^26.6.0" + jest-message-util "^26.6.0" + jest-runner "^26.6.0" + jest-runtime "^26.6.0" + jest-snapshot "^26.6.0" + jest-util "^26.6.0" + pretty-format "^26.6.0" + stack-utils "^2.0.2" + throat "^5.0.0" + +jest-cli@^26.6.0: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a" + integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg== + dependencies: + "@jest/core" "^26.6.3" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + import-local "^3.0.2" + is-ci "^2.0.0" + jest-config "^26.6.3" + jest-util "^26.6.2" + jest-validate "^26.6.2" + prompts "^2.0.1" + yargs "^15.4.1" + +jest-config@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349" + integrity sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg== + dependencies: + "@babel/core" "^7.1.0" + "@jest/test-sequencer" "^26.6.3" + "@jest/types" "^26.6.2" + babel-jest "^26.6.3" + chalk "^4.0.0" + deepmerge "^4.2.2" + glob "^7.1.1" + graceful-fs "^4.2.4" + jest-environment-jsdom "^26.6.2" + jest-environment-node "^26.6.2" + jest-get-type "^26.3.0" + jest-jasmine2 "^26.6.3" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + micromatch "^4.0.2" + pretty-format "^26.6.2" + +jest-diff@^26.0.0, jest-diff@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" + integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== + dependencies: + chalk "^4.0.0" + diff-sequences "^26.6.2" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + +jest-docblock@^26.0.0: + version "26.0.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" + integrity sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w== + dependencies: + detect-newline "^3.0.0" + +jest-each@^26.6.0, jest-each@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb" + integrity sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A== + dependencies: + "@jest/types" "^26.6.2" + chalk "^4.0.0" + jest-get-type "^26.3.0" + jest-util "^26.6.2" + pretty-format "^26.6.2" + +jest-environment-jsdom@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" + integrity sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q== + dependencies: + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + jest-util "^26.6.2" + jsdom "^16.4.0" + +jest-environment-node@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c" + integrity sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag== + dependencies: + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + jest-util "^26.6.2" + +jest-get-type@^26.3.0: + version "26.3.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" + integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== + +jest-haste-map@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" + integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== + dependencies: + "@jest/types" "^26.6.2" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.4" + jest-regex-util "^26.0.0" + jest-serializer "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" + micromatch "^4.0.2" + sane "^4.0.3" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.1.2" + +jest-jasmine2@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd" + integrity sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + expect "^26.6.2" + is-generator-fn "^2.0.0" + jest-each "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + pretty-format "^26.6.2" + throat "^5.0.0" + +jest-leak-detector@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af" + integrity sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg== + dependencies: + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + +jest-matcher-utils@^26.6.0, jest-matcher-utils@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a" + integrity sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw== + dependencies: + chalk "^4.0.0" + jest-diff "^26.6.2" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + +jest-message-util@^26.6.0, jest-message-util@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" + integrity sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/types" "^26.6.2" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + micromatch "^4.0.2" + pretty-format "^26.6.2" + slash "^3.0.0" + stack-utils "^2.0.2" + +jest-mock@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" + integrity sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + +jest-regex-util@^26.0.0: + version "26.0.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" + integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== + +jest-resolve-dependencies@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6" + integrity sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg== + dependencies: + "@jest/types" "^26.6.2" + jest-regex-util "^26.0.0" + jest-snapshot "^26.6.2" + +jest-resolve@26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.6.0.tgz#070fe7159af87b03e50f52ea5e17ee95bbee40e1" + integrity sha512-tRAz2bwraHufNp+CCmAD8ciyCpXCs1NQxB5EJAmtCFy6BN81loFEGWKzYu26Y62lAJJe4X4jg36Kf+NsQyiStQ== + dependencies: + "@jest/types" "^26.6.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + jest-pnp-resolver "^1.2.2" + jest-util "^26.6.0" + read-pkg-up "^7.0.1" + resolve "^1.17.0" + slash "^3.0.0" + +jest-resolve@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" + integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== + dependencies: + "@jest/types" "^26.6.2" + chalk "^4.0.0" + graceful-fs "^4.2.4" + jest-pnp-resolver "^1.2.2" + jest-util "^26.6.2" + read-pkg-up "^7.0.1" + resolve "^1.18.1" + slash "^3.0.0" + +jest-runner@^26.6.0, jest-runner@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159" + integrity sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ== + dependencies: + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.7.1" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-config "^26.6.3" + jest-docblock "^26.0.0" + jest-haste-map "^26.6.2" + jest-leak-detector "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" + jest-runtime "^26.6.3" + jest-util "^26.6.2" + jest-worker "^26.6.2" + source-map-support "^0.5.6" + throat "^5.0.0" + +jest-runtime@^26.6.0, jest-runtime@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b" + integrity sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw== + dependencies: + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/globals" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + cjs-module-lexer "^0.6.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.4" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + slash "^3.0.0" + strip-bom "^4.0.0" + yargs "^15.4.1" + +jest-serializer@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" + integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.4" + +jest-snapshot@^26.6.0, jest-snapshot@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" + integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og== + dependencies: + "@babel/types" "^7.0.0" + "@jest/types" "^26.6.2" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.0.0" + chalk "^4.0.0" + expect "^26.6.2" + graceful-fs "^4.2.4" + jest-diff "^26.6.2" + jest-get-type "^26.3.0" + jest-haste-map "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" + natural-compare "^1.4.0" + pretty-format "^26.6.2" + semver "^7.3.2" + +jest-util@^26.6.0, jest-util@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" + integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + graceful-fs "^4.2.4" + is-ci "^2.0.0" + micromatch "^4.0.2" + +jest-validate@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" + integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ== + dependencies: + "@jest/types" "^26.6.2" + camelcase "^6.0.0" + chalk "^4.0.0" + jest-get-type "^26.3.0" + leven "^3.1.0" + pretty-format "^26.6.2" + +jest-watch-typeahead@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/jest-watch-typeahead/-/jest-watch-typeahead-0.6.1.tgz#45221b86bb6710b7e97baaa1640ae24a07785e63" + integrity sha512-ITVnHhj3Jd/QkqQcTqZfRgjfyRhDFM/auzgVo2RKvSwi18YMvh0WvXDJFoFED6c7jd/5jxtu4kSOb9PTu2cPVg== + dependencies: + ansi-escapes "^4.3.1" + chalk "^4.0.0" + jest-regex-util "^26.0.0" + jest-watcher "^26.3.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + +jest-watcher@^26.3.0, jest-watcher@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975" + integrity sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ== + dependencies: + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + jest-util "^26.6.2" + string-length "^4.0.1" + +jest-worker@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5" + integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw== + dependencies: + merge-stream "^2.0.0" + supports-color "^6.1.0" + +jest-worker@^26.5.0, jest-worker@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^7.0.0" + +jest@26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-26.6.0.tgz#546b25a1d8c888569dbbe93cae131748086a4a25" + integrity sha512-jxTmrvuecVISvKFFhOkjsWRZV7sFqdSUAd1ajOKY+/QE/aLBVstsJ/dX8GczLzwiT6ZEwwmZqtCUHLHHQVzcfA== + dependencies: + "@jest/core" "^26.6.0" + import-local "^3.0.2" + jest-cli "^26.6.0" + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.10.0, js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== +js-yaml@^3.13.1: + version "3.14.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" + integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -4959,36 +8157,36 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdom@^14.1.0: - version "14.1.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-14.1.0.tgz#916463b6094956b0a6c1782c94e380cd30e1981b" - integrity sha512-O901mfJSuTdwU2w3Sn+74T+RnDVP+FuV5fH8tcPWyqrseRAb0s5xOtPgCFiPOtLcyK7CLIJwPyD83ZqQWvA5ng== +jsdom@^16.4.0: + version "16.4.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.4.0.tgz#36005bde2d136f73eee1a830c6d45e55408edddb" + integrity sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w== dependencies: - abab "^2.0.0" - acorn "^6.0.4" - acorn-globals "^4.3.0" - array-equal "^1.0.0" - cssom "^0.3.4" - cssstyle "^1.1.1" - data-urls "^1.1.0" - domexception "^1.0.1" - escodegen "^1.11.0" - html-encoding-sniffer "^1.0.2" - nwsapi "^2.1.3" - parse5 "5.1.0" - pn "^1.1.0" - request "^2.88.0" - request-promise-native "^1.0.5" - saxes "^3.1.9" - symbol-tree "^3.2.2" - tough-cookie "^2.5.0" - w3c-hr-time "^1.0.1" - w3c-xmlserializer "^1.1.2" - webidl-conversions "^4.0.2" + abab "^2.0.3" + acorn "^7.1.1" + acorn-globals "^6.0.0" + cssom "^0.4.4" + cssstyle "^2.2.0" + data-urls "^2.0.0" + decimal.js "^10.2.0" + domexception "^2.0.1" + escodegen "^1.14.1" + html-encoding-sniffer "^2.0.1" + is-potential-custom-element-name "^1.0.0" + nwsapi "^2.2.0" + parse5 "5.1.1" + request "^2.88.2" + request-promise-native "^1.0.8" + saxes "^5.0.0" + symbol-tree "^3.2.4" + tough-cookie "^3.0.1" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^2.0.0" + webidl-conversions "^6.1.0" whatwg-encoding "^1.0.5" whatwg-mimetype "^2.3.0" - whatwg-url "^7.0.0" - ws "^6.1.2" + whatwg-url "^8.0.0" + ws "^7.2.3" xml-name-validator "^3.0.0" jsesc@^2.5.1: @@ -5001,7 +8199,7 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= -json-parse-better-errors@^1.0.1: +json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== @@ -5021,11 +8219,21 @@ json-schema@0.2.3: resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= +json3@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" + integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== + json5@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" @@ -5040,6 +8248,13 @@ json5@^2.1.2: dependencies: minimist "^1.2.5" +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + jsonfile@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" @@ -5059,11 +8274,24 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz#642f1d7b88aa6d7eb9d8f2210e166478444fa891" + integrity sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA== + dependencies: + array-includes "^3.1.1" + object.assign "^4.1.1" + kdbush@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-3.0.0.tgz#f8484794d47004cc2d85ed3a79353dbe0abc2bf0" integrity sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew== +killable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" + integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -5088,11 +8316,49 @@ kind-of@^6.0.0, kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +language-subtag-registry@~0.3.2: + version "0.3.21" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a" + integrity sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg== + +language-tags@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" + integrity sha1-0yHbxNowuovzAk4ED6XBRmH5GTo= + dependencies: + language-subtag-registry "~0.3.2" + +last-call-webpack-plugin@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz#9742df0e10e3cf46e5c0381c2de90d3a7a2d7555" + integrity sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w== + dependencies: + lodash "^4.17.5" + webpack-sources "^1.1.0" + lerp@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/lerp/-/lerp-1.0.3.tgz#a18c8968f917896de15ccfcc28d55a6b731e776e" integrity sha1-oYyJaPkXiW3hXM/MKNVaa3Med24= +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" @@ -5106,30 +8372,75 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -lodash.clone@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" - integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" -lodash.difference@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" - integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw= +loader-runner@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" + integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== -lodash.forown@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.forown/-/lodash.forown-4.4.0.tgz#85115cf04f73ef966eced52511d3893cc46683af" - integrity sha1-hRFc8E9z75ZuztUlEdOJPMRmg68= +loader-utils@1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" + integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== + dependencies: + big.js "^5.2.2" + emojis-list "^2.0.0" + json5 "^1.0.1" -lodash.get@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= +loader-utils@2.0.0, loader-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" + integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" -lodash.groupby@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1" - integrity sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E= +loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" + integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^1.0.1" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash._reinterpolate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= lodash.memoize@^4.1.2: version "4.1.2" @@ -5141,27 +8452,35 @@ lodash.sortby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= -lodash.toarray@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" - integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE= +lodash.template@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" + integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.templatesettings "^4.0.0" + +lodash.templatesettings@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" + integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== + dependencies: + lodash._reinterpolate "^3.0.0" lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4: +"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== -log-symbols@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" - integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== - dependencies: - chalk "^2.0.1" +loglevel@^1.6.8: + version "1.7.0" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0" + integrity sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ== loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" @@ -5170,17 +8489,65 @@ loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lower-case@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.1.tgz#39eeb36e396115cc05e29422eaea9e692c9408c7" + integrity sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ== + dependencies: + tslib "^1.10.0" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + luxon@^1.25.0: version "1.25.0" resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.25.0.tgz#d86219e90bc0102c0eb299d65b2f5e95efe1fe72" integrity sha512-hEgLurSH8kQRjY6i4YLey+mcKVAWXbDNlZRmM6AgWDJ1cY3atl8Ztf5wEY7VBReFbmGnwQPz7KYJblL8B2k0jQ== -magic-string@^0.22.4: - version "0.22.5" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.5.tgz#8e9cf5afddf44385c1da5bc2a6a0dbd10b03657e" - integrity sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w== +lz-string@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" + integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= + +magic-string@^0.25.0, magic-string@^0.25.7: + version "0.25.7" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" + integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== dependencies: - vlq "^0.2.2" + sourcemap-codec "^1.4.4" + +make-dir@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +make-dir@^3.0.0, make-dir@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +makeerror@1.0.x: + version "1.0.11" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw= + dependencies: + tmpl "1.0.x" map-cache@^0.2.2: version "0.2.2" @@ -5297,19 +8664,53 @@ mdn-data@2.0.4: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== -merge-source-map@1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.0.4.tgz#a5de46538dae84d4114cc5ea02b4772a6346701f" - integrity sha1-pd5GU42uhNQRTMXqArR3KmNGcB8= - dependencies: - source-map "^0.5.6" +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -merge2@^1.2.3, merge2@^1.3.0: +memory-fs@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" + integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +memory-fs@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" + integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + +microevent.ts@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/microevent.ts/-/microevent.ts-0.1.1.tgz#70b09b83f43df5172d0205a63025bce0f7357fa0" + integrity sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g== + +micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== @@ -5349,7 +8750,12 @@ mime-db@1.44.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== -mime-types@^2.1.12, mime-types@~2.1.19: +"mime-db@>= 1.43.0 < 2": + version "1.45.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" + integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== + +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.27" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== @@ -5361,10 +8767,30 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mimic-fn@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" - integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== +mime@^2.4.4: + version "2.4.6" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" + integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + +mini-css-extract-plugin@0.11.3: + version "0.11.3" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz#15b0910a7f32e62ffde4a7430cfefbd700724ea6" + integrity sha512-n9BA8LonkOkW1/zn+IbLPQmovsL0wMb9yx75fMJQZf2X1Zoec9yTZtyMePcyu19wPkmFbzZZA6fLTotpFhQsOA== + dependencies: + loader-utils "^1.1.0" + normalize-url "1.9.1" + schema-utils "^1.0.0" + webpack-sources "^1.1.0" minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" @@ -5376,18 +8802,70 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@^3.0.4: +minimatch@3.0.4, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" -minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: +minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.2: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0, minipass@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" + integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== + dependencies: + yallist "^4.0.0" + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mississippi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" + integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== + dependencies: + concat-stream "^1.5.0" + duplexify "^3.4.2" + end-of-stream "^1.1.0" + flush-write-stream "^1.0.0" + from2 "^2.1.0" + parallel-transform "^1.1.0" + pump "^3.0.0" + pumpify "^1.3.3" + stream-each "^1.1.0" + through2 "^2.0.0" + mixin-deep@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" @@ -5396,22 +8874,17 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@^0.5.1, mkdirp@~0.5.1: +mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== dependencies: minimist "^1.2.5" -modern-normalize@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/modern-normalize/-/modern-normalize-1.0.0.tgz#539d84a1e141338b01b346f3e27396d0ed17601e" - integrity sha512-1lM+BMLGuDfsdwf3rsgBSrxJwAZHFIrQ8YR61xIqdHo0uNKI9M52wNpHSrliZATJp51On6JD0AfRxd4YGSU0lw== - -moment@^2.29.1: - version "2.29.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" - integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== +mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== monotone-convex-hull-2d@^1.0.1: version "1.0.1" @@ -5446,6 +8919,18 @@ mouse-wheel@^1.2.0: signum "^1.0.0" to-px "^1.0.1" +move-concurrently@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" + integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I= + dependencies: + aproba "^1.1.1" + copy-concurrently "^1.0.0" + fs-write-stream-atomic "^1.0.8" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.3" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -5456,15 +8941,23 @@ ms@2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -ms@2.1.2: +ms@2.1.2, ms@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +multicast-dns-service-types@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" + integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE= + +multicast-dns@^6.0.1: + version "6.2.3" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" + integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g== + dependencies: + dns-packet "^1.3.1" + thunky "^1.0.2" mumath@^3.3.4: version "3.3.4" @@ -5483,10 +8976,10 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== -nanoid@^3.1.20: - version "3.1.20" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" - integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== +nanoid@^3.1.18: + version "3.1.18" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.18.tgz#0680db22ab01c372e89209f5d18283d98de3e96d" + integrity sha512-rndlDjbbHbcV3xi+R2fpJ+PbGMdfBxz5v1fATIQFq0DP64FsicQdwnKLy47K4kZHdRpmQXtz24eGsxQqamzYTA== nanomatch@^1.2.9: version "1.2.13" @@ -5505,6 +8998,18 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" +native-url@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.2.6.tgz#ca1258f5ace169c716ff44eccbddb674e10399ae" + integrity sha512-k4bDC87WtgrdD362gZz6zoiXQrl40kYlBmpfmSjwRO1VU0V5ccwJTlxuE72F6m3V0vc1xOf6n3UCP9QyerRqmA== + dependencies: + querystring "^0.2.0" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + ndarray-extract-contour@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/ndarray-extract-contour/-/ndarray-extract-contour-1.0.1.tgz#0aee113a3a33b226b90c4888cf877bf4751305e4" @@ -5564,6 +9069,16 @@ ndarray@^1.0.11, ndarray@^1.0.13, ndarray@^1.0.14, ndarray@^1.0.15, ndarray@^1.0 iota-array "^1.0.0" is-buffer "^1.0.2" +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + +neo-async@^2.5.0, neo-async@^2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + next-tick@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" @@ -5581,24 +9096,25 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-addon-api@^1.7.1: - version "1.7.2" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d" - integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg== - -node-emoji@^1.8.1: - version "1.10.0" - resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.10.0.tgz#8886abd25d9c7bb61802a658523d1f8d2a89b2da" - integrity sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw== +no-case@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.3.tgz#c21b434c1ffe48b39087e86cfb4d2582e9df18f8" + integrity sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw== dependencies: - lodash.toarray "^4.4.0" + lower-case "^2.0.1" + tslib "^1.10.0" -node-forge@^0.7.1: - version "0.7.6" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" - integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== +node-forge@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" + integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== -node-libs-browser@^2.0.0: +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + +node-libs-browser@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== @@ -5627,11 +9143,38 @@ node-libs-browser@^2.0.0: util "^0.11.0" vm-browserify "^1.0.1" -node-releases@^1.1.67: +node-modules-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" + integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= + +node-notifier@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.0.tgz#a7eee2d51da6d0f7ff5094bc7108c911240c1620" + integrity sha512-46z7DUmcjoYdaWyXouuFNNfUo6eFa94t23c53c+lG/9Cvauk4a98rAUp9672X5dxGdQmLpPzTxzu8f/OeEPaFA== + dependencies: + growly "^1.3.0" + is-wsl "^2.2.0" + semver "^7.3.2" + shellwords "^0.1.1" + uuid "^8.3.0" + which "^2.0.2" + +node-releases@^1.1.61, node-releases@^1.1.66: version "1.1.67" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.67.tgz#28ebfcccd0baa6aad8e8d4d8fe4cbc49ae239c12" integrity sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg== +normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -5661,6 +9204,16 @@ normalize-svg-path@~0.1.0: resolved "https://registry.yarnpkg.com/normalize-svg-path/-/normalize-svg-path-0.1.0.tgz#456360e60ece75fbef7b5d7e160480e7ffd16fe5" integrity sha1-RWNg5g7Odfvve11+FgSA5//Rb+U= +normalize-url@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" + integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= + dependencies: + object-assign "^4.0.1" + prepend-http "^1.0.0" + query-string "^4.1.0" + sort-keys "^1.0.0" + normalize-url@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" @@ -5671,13 +9224,32 @@ normals@^1.1.0: resolved "https://registry.yarnpkg.com/normals/-/normals-1.1.0.tgz#325b595ed34afe467a6c55a14fd9085787ff59c0" integrity sha1-MltZXtNK/kZ6bFWhT9kIV4f/WcA= -nth-check@^1.0.2: +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + dependencies: + path-key "^2.0.0" + +npm-run-path@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nth-check@^1.0.2, nth-check@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== dependencies: boolbase "~1.0.0" +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= + number-is-integer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-integer/-/number-is-integer-1.0.1.tgz#e59bca172ffed27318e79c7ceb6cb72c095b2152" @@ -5690,7 +9262,7 @@ numeric@^1.2.6: resolved "https://registry.yarnpkg.com/numeric/-/numeric-1.2.6.tgz#765b02bef97988fcf880d4eb3f36b80fa31335aa" integrity sha1-dlsCvvl5iPz4gNTrPza4D6MTNao= -nwsapi@^2.1.3: +nwsapi@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== @@ -5700,7 +9272,7 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -5714,20 +9286,18 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-hash@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.0.3.tgz#d12db044e03cd2ca3d77c0570d87225b02e1e6ea" - integrity sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg== - object-inspect@^1.8.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" - integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== + version "1.8.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" + integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== -object-inspect@~1.4.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.4.1.tgz#37ffb10e71adaf3748d05f713b4c9452f402cbc4" - integrity sha512-wqdhLpfCUbEsoEwl3FXwGyv8ief1k/1aUdIPCqVnupM6e8l63BEJdiF/0swtn04/8p05tG/T0FrpTlfwvljOdw== +object-is@^1.0.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.3.tgz#2e3b9e65560137455ee3bd62aec4d90a2ea1cc81" + integrity sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" object-keys@^1.0.12, object-keys@^1.0.6, object-keys@^1.0.9, object-keys@^1.1.1: version "1.1.1" @@ -5751,14 +9321,32 @@ object.assign@^4.1.0, object.assign@^4.1.1: has-symbols "^1.0.1" object-keys "^1.1.1" -object.getownpropertydescriptors@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.1.tgz#0dfda8d108074d9c563e80490c883b6661091544" - integrity sha512-6DtXgZ/lIZ9hqx4GtZETobXLR/ZLaa0aqV0kzbn80Rf8Z2e/XFnhA0I7p07N2wH8bBBltr2xQPi6sbKWAY2Eng== +object.entries@^1.1.0, object.entries@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" + integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA== dependencies: - call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" + es-abstract "^1.17.5" + has "^1.0.3" + +object.fromentries@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9" + integrity sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" object.pick@^1.3.0: version "1.3.0" @@ -5767,16 +9355,21 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -object.values@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.2.tgz#7a2015e06fcb0f546bd652486ce8583a4731c731" - integrity sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag== +object.values@^1.1.0, object.values@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" + integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== dependencies: - call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" has "^1.0.3" +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -5784,7 +9377,12 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" -once@^1.3.0, once@^1.4.0: +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -5798,20 +9396,36 @@ once@~1.3.0: dependencies: wrappy "1" -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: - mimic-fn "^1.0.0" + mimic-fn "^2.1.0" -opn@^5.1.0: +open@^7.0.2: + version "7.3.0" + resolved "https://registry.yarnpkg.com/open/-/open-7.3.0.tgz#45461fdee46444f3645b6e14eb3ca94b82e1be69" + integrity sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + +opn@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== dependencies: is-wsl "^1.1.0" +optimize-css-assets-webpack-plugin@5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.4.tgz#85883c6528aaa02e30bbad9908c92926bb52dc90" + integrity sha512-wqd6FdI2a5/FdoiCNNkEvLeA//lHHfG24Ln2Xm2qqdIk4aOlsR18jwpyOihqQ8849W3qu2DX8fOYxpvTMj+93A== + dependencies: + cssnano "^4.1.10" + last-call-webpack-plugin "^3.0.0" + optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -5824,17 +9438,17 @@ optionator@^0.8.1: type-check "~0.3.2" word-wrap "~1.2.3" -ora@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ora/-/ora-2.1.0.tgz#6caf2830eb924941861ec53a173799e008b51e5b" - integrity sha512-hNNlAd3gfv/iPmsNxYoAPLvxg7HuPozww7fFonMZvL84tP6Ox5igfk5j/+a9rtJJwqMgKK+JgWsAQik5o0HTLA== +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== dependencies: - chalk "^2.3.1" - cli-cursor "^2.1.0" - cli-spinners "^1.1.0" - log-symbols "^2.2.0" - strip-ansi "^4.0.0" - wcwidth "^1.0.1" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" orbit-camera-controller@^4.0.0: version "4.0.0" @@ -5844,11 +9458,99 @@ orbit-camera-controller@^4.0.0: filtered-vector "^1.2.1" gl-mat4 "^1.0.3" +original@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" + integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg== + dependencies: + url-parse "^1.4.3" + os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= +p-each-series@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" + integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA== + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-limit@^2.0.0, p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.0.2.tgz#1664e010af3cadc681baafd3e2a437be7b0fb5fe" + integrity sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg== + dependencies: + p-try "^2.0.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-map@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" + integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-retry@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328" + integrity sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w== + dependencies: + retry "^0.12.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + pad-left@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pad-left/-/pad-left-1.0.2.tgz#19e5735ea98395a26cedc6ab926ead10f3100d4c" @@ -5856,80 +9558,27 @@ pad-left@^1.0.2: dependencies: repeat-string "^1.3.0" -pako@^0.2.5: - version "0.2.9" - resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" - integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU= - pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== -parcel-bundler@^1.12.4: - version "1.12.4" - resolved "https://registry.yarnpkg.com/parcel-bundler/-/parcel-bundler-1.12.4.tgz#31223f4ab4d00323a109fce28d5e46775409a9ee" - integrity sha512-G+iZGGiPEXcRzw0fiRxWYCKxdt/F7l9a0xkiU4XbcVRJCSlBnioWEwJMutOCCpoQmaQtjB4RBHDGIHN85AIhLQ== +parallel-transform@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" + integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/core" "^7.4.4" - "@babel/generator" "^7.4.4" - "@babel/parser" "^7.4.4" - "@babel/plugin-transform-flow-strip-types" "^7.4.4" - "@babel/plugin-transform-modules-commonjs" "^7.4.4" - "@babel/plugin-transform-react-jsx" "^7.0.0" - "@babel/preset-env" "^7.4.4" - "@babel/runtime" "^7.4.4" - "@babel/template" "^7.4.4" - "@babel/traverse" "^7.4.4" - "@babel/types" "^7.4.4" - "@iarna/toml" "^2.2.0" - "@parcel/fs" "^1.11.0" - "@parcel/logger" "^1.11.1" - "@parcel/utils" "^1.11.0" - "@parcel/watcher" "^1.12.1" - "@parcel/workers" "^1.11.0" - ansi-to-html "^0.6.4" - babylon-walk "^1.0.2" - browserslist "^4.1.0" - chalk "^2.1.0" - clone "^2.1.1" - command-exists "^1.2.6" - commander "^2.11.0" - core-js "^2.6.5" - cross-spawn "^6.0.4" - css-modules-loader-core "^1.1.0" - cssnano "^4.0.0" - deasync "^0.1.14" - dotenv "^5.0.0" - dotenv-expand "^5.1.0" - envinfo "^7.3.1" - fast-glob "^2.2.2" - filesize "^3.6.0" - get-port "^3.2.0" - htmlnano "^0.2.2" - is-glob "^4.0.0" - is-url "^1.2.2" - js-yaml "^3.10.0" - json5 "^1.0.1" - micromatch "^3.0.4" - mkdirp "^0.5.1" - node-forge "^0.7.1" - node-libs-browser "^2.0.0" - opn "^5.1.0" - postcss "^7.0.11" - postcss-value-parser "^3.3.1" - posthtml "^0.11.2" - posthtml-parser "^0.4.0" - posthtml-render "^1.1.3" - resolve "^1.4.0" - semver "^5.4.1" - serialize-to-js "^3.0.0" - serve-static "^1.12.4" - source-map "0.6.1" - terser "^3.7.3" - v8-compile-cache "^2.0.0" - ws "^5.1.1" + cyclist "^1.0.1" + inherits "^2.0.3" + readable-stream "^2.1.5" + +param-case@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.3.tgz#4be41f8399eff621c56eebb829a5e451d9801238" + integrity sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA== + dependencies: + dot-case "^3.0.3" + tslib "^1.10.0" parent-module@^1.0.0: version "1.0.1" @@ -5954,6 +9603,13 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= + dependencies: + error-ex "^1.2.0" + parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" @@ -5989,10 +9645,10 @@ parse-unit@^1.0.1: resolved "https://registry.yarnpkg.com/parse-unit/-/parse-unit-1.0.1.tgz#7e1bb6d5bef3874c28e392526a2541170291eecf" integrity sha1-fhu21b7zh0wo45JSaiVBFwKR7s8= -parse5@5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" - integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== +parse5@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" + integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== parseqs@0.0.6: version "0.0.6" @@ -6004,11 +9660,19 @@ parseuri@0.0.6: resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.6.tgz#e1496e829e3ac2ff47f39a4dd044b32823c4a25a" integrity sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow== -parseurl@~1.3.3: +parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +pascal-case@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.1.tgz#5ac1975133ed619281e88920973d2cd1f279de5f" + integrity sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA== + dependencies: + no-case "^3.0.3" + tslib "^1.10.0" + pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" @@ -6024,21 +9688,53 @@ path-dirname@^1.0.0: resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-key@^2.0.1: +path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= + +path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= + dependencies: + pify "^2.0.0" + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -6083,26 +9779,73 @@ permutation-rank@^1.0.0: invert-permutation "^1.0.0" typedarray-pool "^1.0.0" -physical-cpu-count@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/physical-cpu-count/-/physical-cpu-count-2.0.0.tgz#18de2f97e4bf7a9551ad7511942b5496f7aba660" - integrity sha1-GN4vl+S/epVRrXURlCtUlverpmA= - pick-by-alias@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pick-by-alias/-/pick-by-alias-1.2.0.tgz#5f7cb2b1f21a6e1e884a0c87855aa4a37361107b" integrity sha1-X3yysfIabh6ISgyHhVqko3NhEHs= -picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: +picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== -pify@^2.3.0: +pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= + +pirates@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" + integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== + dependencies: + node-modules-regexp "^1.0.0" + +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= + dependencies: + find-up "^2.1.0" + +pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" + integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== + dependencies: + find-up "^3.0.0" + +pkg-dir@^4.1.0, pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pkg-up@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" + integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== + dependencies: + find-up "^3.0.0" + planar-dual@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/planar-dual/-/planar-dual-1.0.2.tgz#b6a4235523b1b0cb79e5f926f8ea335dd982d563" @@ -6124,10 +9867,10 @@ planar-graph-to-polyline@^1.0.0: two-product "^1.0.0" uniq "^1.0.0" -plotly.js@^1.58.2: - version "1.58.2" - resolved "https://registry.yarnpkg.com/plotly.js/-/plotly.js-1.58.2.tgz#898a0c04908d7eee508d628eae017e1f4198930c" - integrity sha512-CQ1Fg50BafIeFs3PQ8D2byigrmn5UoOMJHgyLBcYoHxxQTI9L85xKl02EkiJxg7KJUgNr//Bt/yu8heAIy10XQ== +plotly.js@^1.58.4: + version "1.58.4" + resolved "https://registry.yarnpkg.com/plotly.js/-/plotly.js-1.58.4.tgz#9aa9cab814b8ae4fef11fac995157b3bb94dfdd6" + integrity sha512-hdt/aEvkPjS1HJ7tJKcPqsqi9ErEZPhUFs4d2ANTLeBim+AmVcHzS1rtwr7ZrVCINgliW/+92u81omJoy+lbUw== dependencies: "@plotly/d3-sankey" "0.7.2" "@plotly/d3-sankey-circular" "0.33.1" @@ -6198,10 +9941,12 @@ plotly.js@^1.58.2: webgl-context "^2.2.0" world-calendars "^1.0.3" -pn@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" - integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== +pnp-webpack-plugin@1.6.4: + version "1.6.4" + resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149" + integrity sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg== + dependencies: + ts-pnp "^1.1.6" point-in-big-polygon@^2.0.0: version "2.0.0" @@ -6225,11 +9970,35 @@ polytope-closest-point@^1.0.0: dependencies: numeric "^1.2.6" +portfinder@^1.0.26: + version "1.0.28" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" + integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== + dependencies: + async "^2.6.2" + debug "^3.1.1" + mkdirp "^0.5.5" + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= +postcss-attribute-case-insensitive@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" + integrity sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^6.0.2" + +postcss-browser-comments@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-browser-comments/-/postcss-browser-comments-3.0.0.tgz#1248d2d935fb72053c8e1f61a84a57292d9f65e9" + integrity sha512-qfVjLfq7HFd2e0HW4s1dvU8X080OZdG46fFbIBFjW7US7YPDcWfRvdElvwMJr2LI6hMmD+7LnH2HcmXTs+uOig== + dependencies: + postcss "^7" + postcss-calc@^7.0.1: version "7.0.5" resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.5.tgz#f8a6e99f12e619c2ebc23cf6c486fdc15860933e" @@ -6239,23 +10008,47 @@ postcss-calc@^7.0.1: postcss-selector-parser "^6.0.2" postcss-value-parser "^4.0.2" -postcss-cli@^8.3.1: - version "8.3.1" - resolved "https://registry.yarnpkg.com/postcss-cli/-/postcss-cli-8.3.1.tgz#865dad08300ac59ae9cecb7066780aa81c767a77" - integrity sha512-leHXsQRq89S3JC9zw/tKyiVV2jAhnfQe0J8VI4eQQbUjwIe0XxVqLrR+7UsahF1s9wi4GlqP6SJ8ydf44cgF2Q== +postcss-color-functional-notation@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz#5efd37a88fbabeb00a2966d1e53d98ced93f74e0" + integrity sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g== dependencies: - chalk "^4.0.0" - chokidar "^3.3.0" - dependency-graph "^0.9.0" - fs-extra "^9.0.0" - get-stdin "^8.0.0" - globby "^11.0.0" - postcss-load-config "^3.0.0" - postcss-reporter "^7.0.0" - pretty-hrtime "^1.0.3" - read-cache "^1.0.0" - slash "^3.0.0" - yargs "^16.0.0" + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-color-gray@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz#532a31eb909f8da898ceffe296fdc1f864be8547" + integrity sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.5" + postcss-values-parser "^2.0.0" + +postcss-color-hex-alpha@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz#a8d9ca4c39d497c9661e374b9c51899ef0f87388" + integrity sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw== + dependencies: + postcss "^7.0.14" + postcss-values-parser "^2.0.1" + +postcss-color-mod-function@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz#816ba145ac11cc3cb6baa905a75a49f903e4d31d" + integrity sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-color-rebeccapurple@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz#c7a89be872bb74e45b1e3022bfe5748823e6de77" + integrity sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" postcss-colormin@^4.0.3: version "4.0.3" @@ -6276,6 +10069,37 @@ postcss-convert-values@^4.0.1: postcss "^7.0.0" postcss-value-parser "^3.0.0" +postcss-custom-media@^7.0.8: + version "7.0.8" + resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz#fffd13ffeffad73621be5f387076a28b00294e0c" + integrity sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg== + dependencies: + postcss "^7.0.14" + +postcss-custom-properties@^8.0.11: + version "8.0.11" + resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz#2d61772d6e92f22f5e0d52602df8fae46fa30d97" + integrity sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA== + dependencies: + postcss "^7.0.17" + postcss-values-parser "^2.0.1" + +postcss-custom-selectors@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz#64858c6eb2ecff2fb41d0b28c9dd7b3db4de7fba" + integrity sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + +postcss-dir-pseudo-class@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz#6e3a4177d0edb3abcc85fdb6fbb1c26dabaeaba2" + integrity sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + postcss-discard-comments@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" @@ -6304,31 +10128,113 @@ postcss-discard-overridden@^4.0.1: dependencies: postcss "^7.0.0" -postcss-functions@^3: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-functions/-/postcss-functions-3.0.0.tgz#0e94d01444700a481de20de4d55fb2640564250e" - integrity sha1-DpTQFERwCkgd4g3k1V+yZAVkJQ4= +postcss-double-position-gradients@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz#fc927d52fddc896cb3a2812ebc5df147e110522e" + integrity sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA== dependencies: - glob "^7.1.2" - object-assign "^4.1.1" - postcss "^6.0.9" - postcss-value-parser "^3.3.0" + postcss "^7.0.5" + postcss-values-parser "^2.0.0" -postcss-js@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-3.0.3.tgz#2f0bd370a2e8599d45439f6970403b5873abda33" - integrity sha512-gWnoWQXKFw65Hk/mi2+WTQTHdPD5UJdDXZmX073EY/B3BWnYjO4F4t0VneTCnCGQ5E5GsCdMkzPaTXwl3r5dJw== +postcss-env-function@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-env-function/-/postcss-env-function-2.0.2.tgz#0f3e3d3c57f094a92c2baf4b6241f0b0da5365d7" + integrity sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw== dependencies: - camelcase-css "^2.0.1" - postcss "^8.1.6" + postcss "^7.0.2" + postcss-values-parser "^2.0.0" -postcss-load-config@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.0.0.tgz#850bb066edd65b734329eacf83af0c0764226c87" - integrity sha512-lErrN8imuEF1cSiHBV8MiR7HeuzlDpCGNtaMyYHlOBuJHHOGw6S4xOMZp8BbXPr7AGQp14L6PZDlIOpfFJ6f7w== +postcss-flexbugs-fixes@4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.2.1.tgz#9218a65249f30897deab1033aced8578562a6690" + integrity sha512-9SiofaZ9CWpQWxOwRh1b/r85KD5y7GgvsNt1056k6OYLvWUun0czCvogfJgylC22uJTwW1KzY3Gz65NZRlvoiQ== dependencies: - cosmiconfig "^7.0.0" - import-cwd "^3.0.0" + postcss "^7.0.26" + +postcss-focus-visible@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz#477d107113ade6024b14128317ade2bd1e17046e" + integrity sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g== + dependencies: + postcss "^7.0.2" + +postcss-focus-within@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz#763b8788596cee9b874c999201cdde80659ef680" + integrity sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w== + dependencies: + postcss "^7.0.2" + +postcss-font-variant@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-4.0.1.tgz#42d4c0ab30894f60f98b17561eb5c0321f502641" + integrity sha512-I3ADQSTNtLTTd8uxZhtSOrTCQ9G4qUVKPjHiDk0bV75QSxXjVWiJVJ2VLdspGUi9fbW9BcjKJoRvxAH1pckqmA== + dependencies: + postcss "^7.0.2" + +postcss-gap-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz#431c192ab3ed96a3c3d09f2ff615960f902c1715" + integrity sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg== + dependencies: + postcss "^7.0.2" + +postcss-image-set-function@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz#28920a2f29945bed4c3198d7df6496d410d3f288" + integrity sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-initial@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-3.0.2.tgz#f018563694b3c16ae8eaabe3c585ac6319637b2d" + integrity sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA== + dependencies: + lodash.template "^4.5.0" + postcss "^7.0.2" + +postcss-lab-function@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz#bb51a6856cd12289ab4ae20db1e3821ef13d7d2e" + integrity sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-load-config@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz#c5ea504f2c4aef33c7359a34de3573772ad7502a" + integrity sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw== + dependencies: + cosmiconfig "^5.0.0" + import-cwd "^2.0.0" + +postcss-loader@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-3.0.0.tgz#6b97943e47c72d845fa9e03f273773d4e8dd6c2d" + integrity sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA== + dependencies: + loader-utils "^1.1.0" + postcss "^7.0.0" + postcss-load-config "^2.0.0" + schema-utils "^1.0.0" + +postcss-logical@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-3.0.0.tgz#2495d0f8b82e9f262725f75f9401b34e7b45d5b5" + integrity sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA== + dependencies: + postcss "^7.0.2" + +postcss-media-minmax@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz#b75bb6cbc217c8ac49433e12f22048814a4f5ed5" + integrity sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw== + dependencies: + postcss "^7.0.2" postcss-merge-longhand@^4.0.11: version "4.0.11" @@ -6392,43 +10298,45 @@ postcss-minify-selectors@^4.0.2: postcss "^7.0.0" postcss-selector-parser "^3.0.0" -postcss-modules-extract-imports@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz#b614c9720be6816eaee35fb3a5faa1dba6a05ddb" - integrity sha1-thTJcgvmgW6u41+zpfqh26agXds= +postcss-modules-extract-imports@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" + integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ== dependencies: - postcss "^6.0.1" + postcss "^7.0.5" -postcss-modules-local-by-default@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" - integrity sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk= +postcss-modules-local-by-default@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0" + integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw== dependencies: - css-selector-tokenizer "^0.7.0" - postcss "^6.0.1" + icss-utils "^4.1.1" + postcss "^7.0.32" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.1.0" -postcss-modules-scope@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" - integrity sha1-1upkmUx5+XtipytCb75gVqGUu5A= +postcss-modules-scope@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee" + integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ== dependencies: - css-selector-tokenizer "^0.7.0" - postcss "^6.0.1" + postcss "^7.0.6" + postcss-selector-parser "^6.0.0" -postcss-modules-values@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20" - integrity sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA= +postcss-modules-values@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10" + integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg== dependencies: - icss-replace-symbols "^1.1.0" - postcss "^6.0.1" + icss-utils "^4.0.0" + postcss "^7.0.6" -postcss-nested@^5.0.1: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.3.tgz#2f46d77a06fc98d9c22344fd097396f5431386db" - integrity sha512-R2LHPw+u5hFfDgJG748KpGbJyTv7Yr33/2tIMWxquYuHTd9EXu27PYnKi7BxMXLtzKC0a0WVsqHtd7qIluQu/g== +postcss-nesting@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-7.0.1.tgz#b50ad7b7f0173e5b5e3880c3501344703e04c052" + integrity sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg== dependencies: - postcss-selector-parser "^6.0.4" + postcss "^7.0.2" postcss-normalize-charset@^4.0.1: version "4.0.1" @@ -6511,6 +10419,17 @@ postcss-normalize-whitespace@^4.0.2: postcss "^7.0.0" postcss-value-parser "^3.0.0" +postcss-normalize@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize/-/postcss-normalize-8.0.1.tgz#90e80a7763d7fdf2da6f2f0f82be832ce4f66776" + integrity sha512-rt9JMS/m9FHIRroDDBGSMsyW1c0fkvOJPy62ggxSHUldJO7B195TqFMqIf+lY5ezpDcYOV4j86aUp3/XbxzCCQ== + dependencies: + "@csstools/normalize.css" "^10.1.0" + browserslist "^4.6.2" + postcss "^7.0.17" + postcss-browser-comments "^3.0.0" + sanitize.css "^10.0.0" + postcss-ordered-values@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" @@ -6520,6 +10439,79 @@ postcss-ordered-values@^4.1.2: postcss "^7.0.0" postcss-value-parser "^3.0.0" +postcss-overflow-shorthand@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz#31ecf350e9c6f6ddc250a78f0c3e111f32dd4c30" + integrity sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g== + dependencies: + postcss "^7.0.2" + +postcss-page-break@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-2.0.0.tgz#add52d0e0a528cabe6afee8b46e2abb277df46bf" + integrity sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ== + dependencies: + postcss "^7.0.2" + +postcss-place@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-4.0.1.tgz#e9f39d33d2dc584e46ee1db45adb77ca9d1dcc62" + integrity sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-preset-env@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz#c34ddacf8f902383b35ad1e030f178f4cdf118a5" + integrity sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg== + dependencies: + autoprefixer "^9.6.1" + browserslist "^4.6.4" + caniuse-lite "^1.0.30000981" + css-blank-pseudo "^0.1.4" + css-has-pseudo "^0.10.0" + css-prefers-color-scheme "^3.1.1" + cssdb "^4.4.0" + postcss "^7.0.17" + postcss-attribute-case-insensitive "^4.0.1" + postcss-color-functional-notation "^2.0.1" + postcss-color-gray "^5.0.0" + postcss-color-hex-alpha "^5.0.3" + postcss-color-mod-function "^3.0.3" + postcss-color-rebeccapurple "^4.0.1" + postcss-custom-media "^7.0.8" + postcss-custom-properties "^8.0.11" + postcss-custom-selectors "^5.1.2" + postcss-dir-pseudo-class "^5.0.0" + postcss-double-position-gradients "^1.0.0" + postcss-env-function "^2.0.2" + postcss-focus-visible "^4.0.0" + postcss-focus-within "^3.0.0" + postcss-font-variant "^4.0.0" + postcss-gap-properties "^2.0.0" + postcss-image-set-function "^3.0.1" + postcss-initial "^3.0.0" + postcss-lab-function "^2.0.1" + postcss-logical "^3.0.0" + postcss-media-minmax "^4.0.0" + postcss-nesting "^7.0.0" + postcss-overflow-shorthand "^2.0.0" + postcss-page-break "^2.0.0" + postcss-place "^4.0.1" + postcss-pseudo-class-any-link "^6.0.0" + postcss-replace-overflow-wrap "^3.0.0" + postcss-selector-matches "^4.0.0" + postcss-selector-not "^4.0.0" + +postcss-pseudo-class-any-link@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz#2ed3eed393b3702879dec4a87032b210daeb04d1" + integrity sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + postcss-reduce-initial@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" @@ -6540,26 +10532,35 @@ postcss-reduce-transforms@^4.0.2: postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-reporter@^7.0.0: - version "7.0.2" - resolved "https://registry.yarnpkg.com/postcss-reporter/-/postcss-reporter-7.0.2.tgz#03e9e7381c1afe40646f9c22e7aeeb860e051065" - integrity sha512-JyQ96NTQQsso42y6L1H1RqHfWH1C3Jr0pt91mVv5IdYddZAE9DUZxuferNgk6q0o6vBVOrfVJb10X1FgDzjmDw== +postcss-replace-overflow-wrap@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz#61b360ffdaedca84c7c918d2b0f0d0ea559ab01c" + integrity sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw== dependencies: - colorette "^1.2.1" - lodash.difference "^4.5.0" - lodash.forown "^4.4.0" - lodash.get "^4.4.2" - lodash.groupby "^4.6.0" - lodash.sortby "^4.7.0" + postcss "^7.0.2" -postcss-selector-parser@6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" - integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== +postcss-safe-parser@5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-5.0.2.tgz#459dd27df6bc2ba64608824ba39e45dacf5e852d" + integrity sha512-jDUfCPJbKOABhwpUKcqCVbbXiloe/QXMcbJ6Iipf3sDIihEzTqRCeMBfRaOHxhBuTYqtASrI1KJWxzztZU4qUQ== dependencies: - cssesc "^3.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" + postcss "^8.1.0" + +postcss-selector-matches@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz#71c8248f917ba2cc93037c9637ee09c64436fcff" + integrity sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww== + dependencies: + balanced-match "^1.0.0" + postcss "^7.0.2" + +postcss-selector-not@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz#c68ff7ba96527499e832724a2674d65603b645c0" + integrity sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ== + dependencies: + balanced-match "^1.0.0" + postcss "^7.0.2" postcss-selector-parser@^3.0.0: version "3.1.2" @@ -6570,7 +10571,16 @@ postcss-selector-parser@^3.0.0: indexes-of "^1.0.1" uniq "^1.0.1" -postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: +postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c" + integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ== + dependencies: + cssesc "^2.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: version "6.0.4" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3" integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw== @@ -6599,7 +10609,7 @@ postcss-unique-selectors@^4.0.1: postcss "^7.0.0" uniqs "^2.0.0" -postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.0, postcss-value-parser@^3.3.1: +postcss-value-parser@^3.0.0: version "3.3.1" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== @@ -6609,34 +10619,25 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== -postcss@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.1.tgz#000dbd1f8eef217aa368b9a212c5fc40b2a8f3f2" - integrity sha1-AA29H47vIXqjaLmiEsX8QLKo8/I= +postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f" + integrity sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg== dependencies: - chalk "^1.1.3" - source-map "^0.5.6" - supports-color "^3.2.3" + flatten "^1.0.2" + indexes-of "^1.0.1" + uniq "^1.0.1" -postcss@7.0.32: - version "7.0.32" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d" - integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== +postcss@7.0.21: + version "7.0.21" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.21.tgz#06bb07824c19c2021c5d056d5b10c35b989f7e17" + integrity sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ== dependencies: chalk "^2.4.2" source-map "^0.6.1" supports-color "^6.1.0" -postcss@^6.0.1, postcss@^6.0.9: - version "6.0.23" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" - integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== - dependencies: - chalk "^2.4.1" - source-map "^0.6.1" - supports-color "^5.4.0" - -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.11, postcss@^7.0.17, postcss@^7.0.27: +postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: version "7.0.35" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24" integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg== @@ -6645,64 +10646,58 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.11, postcss@^7.0.17, postcss@^7.0.2 source-map "^0.6.1" supports-color "^6.1.0" -postcss@^8.1.6, postcss@^8.2.1: - version "8.2.1" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.1.tgz#eabc5557c4558059b9d9e5b15bce7ffa9089c2a8" - integrity sha512-RhsqOOAQzTgh1UB/IZdca7F9WDb7SUCR2Vnv1x7DbvuuggQIpoDwjK+q0rzoPffhYvWNKX5JSwS4so4K3UC6vA== +postcss@^8.1.0: + version "8.1.9" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.1.9.tgz#20ff4b598a6f5015c5f7fe524b8ed5313d7ecade" + integrity sha512-oWuBpEl1meaMKkQXn0ic78TUrgsMvrAZLE/6ZY0H3LTteq2O3L8PGWwMbPLctpksTJIHjQeossMUMNQW7qRIHQ== dependencies: colorette "^1.2.1" - nanoid "^3.1.20" + nanoid "^3.1.18" source-map "^0.6.1" - -posthtml-parser@^0.4.0, posthtml-parser@^0.4.1: - version "0.4.2" - resolved "https://registry.yarnpkg.com/posthtml-parser/-/posthtml-parser-0.4.2.tgz#a132bbdf0cd4bc199d34f322f5c1599385d7c6c1" - integrity sha512-BUIorsYJTvS9UhXxPTzupIztOMVNPa/HtAm9KHni9z6qEfiJ1bpOBL5DfUOL9XAc3XkLIEzBzpph+Zbm4AdRAg== - dependencies: - htmlparser2 "^3.9.2" - -posthtml-parser@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/posthtml-parser/-/posthtml-parser-0.5.3.tgz#e95b92e57d98da50b443e116fcee39466cd9012e" - integrity sha512-uHosRn0y+1wbnlYKrqMjBPoo/kK5LPYImLtiETszNFYfFwAD3cQdD1R2E13Mh5icBxkHj+yKtlIHozCsmVWD/Q== - dependencies: - htmlparser2 "^3.9.2" - -posthtml-render@^1.1.3, posthtml-render@^1.1.5, posthtml-render@^1.2.3, posthtml-render@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/posthtml-render/-/posthtml-render-1.4.0.tgz#40114070c45881cacb93347dae3eff53afbcff13" - integrity sha512-W1779iVHGfq0Fvh2PROhCe2QhB8mEErgqzo1wpIt36tCgChafP+hbXIhLDOM8ePJrZcFs0vkNEtdibEWVqChqw== - -posthtml@^0.11.2: - version "0.11.6" - resolved "https://registry.yarnpkg.com/posthtml/-/posthtml-0.11.6.tgz#e349d51af7929d0683b9d8c3abd8166beecc90a8" - integrity sha512-C2hrAPzmRdpuL3iH0TDdQ6XCc9M7Dcc3zEW5BLerY65G4tWWszwv6nG/ksi6ul5i2mx22ubdljgktXCtNkydkw== - dependencies: - posthtml-parser "^0.4.1" - posthtml-render "^1.1.5" - -posthtml@^0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/posthtml/-/posthtml-0.13.4.tgz#ad81b3fa62b85f81ccdb5710f4ec375a4ed94934" - integrity sha512-i2oTo/+dwXGC6zaAQSF6WZEQSbEqu10hsvg01DWzGAfZmy31Iiy9ktPh9nnXDfZiYytjxTIvxoK4TI0uk4QWpw== - dependencies: - posthtml-parser "^0.5.0" - posthtml-render "^1.2.3" + vfile-location "^3.2.0" potpack@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.1.tgz#d1b1afd89e4c8f7762865ec30bd112ab767e2ebf" integrity sha512-15vItUAbViaYrmaB/Pbw7z6qX2xENbFSTA7Ii4tgbPtasxm5v6ryKhKtL91tpWovDJzTiZqdwzhcFBCwiMVdVw== +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -pretty-hrtime@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" - integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= +prepend-http@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= + +pretty-bytes@^5.3.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.4.1.tgz#cd89f79bbcef21e3d21eb0da68ffe93f803e884b" + integrity sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA== + +pretty-error@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.2.tgz#be89f82d81b1c86ec8fdfbc385045882727f93b6" + integrity sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw== + dependencies: + lodash "^4.17.20" + renderkid "^2.0.4" + +pretty-format@^26.0.0, pretty-format@^26.6.0, pretty-format@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" + integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== + dependencies: + "@jest/types" "^26.6.2" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^17.0.1" process-nextick-args@~2.0.0: version "2.0.1" @@ -6714,6 +10709,31 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= + +promise@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" + integrity sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q== + dependencies: + asap "~2.0.6" + +prompts@2.4.0, prompts@^2.0.1: + version "2.4.0" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.0.tgz#4aa5de0723a231d1ee9121c40fdf663df73f61d7" + integrity sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" @@ -6728,6 +10748,19 @@ protocol-buffers-schema@^3.3.1: resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.4.0.tgz#2f0ea31ca96627d680bf2fefae7ebfa2b6453eae" integrity sha512-G/2kcamPF2S49W5yaMGdIpkG6+5wZF0fzBteLKgEHjbNzqjZQ85aAs1iJGto31EJaSTkNvHs5IXuHSaTLWBAiA== +proxy-addr@~2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" + integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.9.1" + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= + psl@^1.1.28: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -6745,6 +10778,31 @@ public-encrypt@^4.0.0: randombytes "^2.0.1" safe-buffer "^5.1.2" +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.3: + version "1.5.1" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" + integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== + dependencies: + duplexify "^3.6.0" + inherits "^2.0.3" + pump "^2.0.0" + punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" @@ -6760,26 +10818,6 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -purgecss@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-2.3.0.tgz#5327587abf5795e6541517af8b190a6fb5488bb3" - integrity sha512-BE5CROfVGsx2XIhxGuZAT7rTH9lLeQx/6M0P7DTXQH4IUc3BBzs9JUzt4yzGf3JrH9enkeq6YJBe9CTtkm1WmQ== - dependencies: - commander "^5.0.0" - glob "^7.0.0" - postcss "7.0.32" - postcss-selector-parser "^6.0.2" - -purgecss@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-3.1.3.tgz#26987ec09d12eeadc318e22f6e5a9eb0be094f41" - integrity sha512-hRSLN9mguJ2lzlIQtW4qmPS2kh6oMnA9RxdIYK8sz18QYqd6ePp4GNDl18oWHA1f2v2NEQIh51CO8s/E3YGckQ== - dependencies: - commander "^6.0.0" - glob "^7.0.0" - postcss "^8.2.1" - postcss-selector-parser "^6.0.2" - pxls@^2.0.0: version "2.3.2" resolved "https://registry.yarnpkg.com/pxls/-/pxls-2.3.2.tgz#79100d2cc95089fc6e00053a9d93c1ddddb2c7b4" @@ -6797,6 +10835,11 @@ q@^1.1.2: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= +qs@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== + qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -6814,30 +10857,34 @@ quat-slerp@^1.0.0: dependencies: gl-quat "^1.0.0" +query-string@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= + dependencies: + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= -querystring@0.2.0: +querystring@0.2.0, querystring@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + quickselect@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018" integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw== -quote-stream@^1.0.1, quote-stream@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/quote-stream/-/quote-stream-1.0.2.tgz#84963f8c9c26b942e153feeb53aae74652b7e0b2" - integrity sha1-hJY/jJwmuULhU/7rU6rnRlK34LI= - dependencies: - buffer-equal "0.0.1" - minimist "^1.1.3" - through2 "^2.0.0" - raf@^3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" @@ -6845,7 +10892,7 @@ raf@^3.4.1: dependencies: performance-now "^2.1.0" -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== @@ -6860,7 +10907,7 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" -range-parser@~1.2.1: +range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== @@ -6872,11 +10919,63 @@ rat-vec@^1.1.1: dependencies: big-rat "^1.0.3" +raw-body@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" + integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== + dependencies: + bytes "3.1.0" + http-errors "1.7.2" + iconv-lite "0.4.24" + unpipe "1.0.0" + +react-app-polyfill@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-2.0.0.tgz#a0bea50f078b8a082970a9d853dc34b6dcc6a3cf" + integrity sha512-0sF4ny9v/B7s6aoehwze9vJNWcmCemAUYBVasscVr92+UYiEqDXOxfKjXN685mDaMRNF3WdhHQs76oTODMocFA== + dependencies: + core-js "^3.6.5" + object-assign "^4.1.1" + promise "^8.1.0" + raf "^3.4.1" + regenerator-runtime "^0.13.7" + whatwg-fetch "^3.4.1" + react-cryptocoins@^1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/react-cryptocoins/-/react-cryptocoins-1.0.11.tgz#c2f3ea9303538fba9179bc39e4ecb237f7b5f589" integrity sha512-9HAqYA718bBcYYtGPLPOtlAAQZn/fSgLxxfX5RIYN+XZkZC7wDU1UV+hHoj9/D35zOAWxZyuJKK3g3haZtZSiA== +react-dev-utils@^11.0.1: + version "11.0.1" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-11.0.1.tgz#30106c2055acfd6b047d2dc478a85c356e66fe45" + integrity sha512-rlgpCupaW6qQqvu0hvv2FDv40QG427fjghV56XyPcP5aKtOAPzNAhQ7bHqk1YdS2vpW1W7aSV3JobedxuPlBAA== + dependencies: + "@babel/code-frame" "7.10.4" + address "1.1.2" + browserslist "4.14.2" + chalk "2.4.2" + cross-spawn "7.0.3" + detect-port-alt "1.1.6" + escape-string-regexp "2.0.0" + filesize "6.1.0" + find-up "4.1.0" + fork-ts-checker-webpack-plugin "4.1.6" + global-modules "2.0.0" + globby "11.0.1" + gzip-size "5.1.1" + immer "7.0.9" + is-root "2.1.0" + loader-utils "2.0.0" + open "^7.0.2" + pkg-up "3.1.0" + prompts "2.4.0" + react-error-overlay "^6.0.8" + recursive-readdir "2.2.2" + shell-quote "1.7.2" + strip-ansi "6.0.0" + text-table "0.2.0" + react-dom@^17.0.1: version "17.0.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.1.tgz#1de2560474ec9f0e334285662ede52dbc5426fc6" @@ -6886,6 +10985,11 @@ react-dom@^17.0.1: object-assign "^4.1.1" scheduler "^0.20.1" +react-error-overlay@^6.0.8: + version "6.0.8" + resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.8.tgz#474ed11d04fc6bda3af643447d85e9127ed6b5de" + integrity sha512-HvPuUQnLp5H7TouGq3kzBeioJmXms1wHy9EGjz2OURWBp4qZO6AfGEcnxts1D/CbwPLRAgTMPCEgYhA3sEM4vw== + react-fast-compare@^3.1.1: version "3.2.0" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" @@ -6906,6 +11010,11 @@ react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-is@^17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" + integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== + react-plotly.js@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/react-plotly.js/-/react-plotly.js-2.5.1.tgz#11182bf599ef11a0dbfcd171c6f5645535a2b486" @@ -6913,6 +11022,82 @@ react-plotly.js@^2.5.1: dependencies: prop-types "^15.7.2" +react-plotly@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/react-plotly/-/react-plotly-1.0.0.tgz#addff804d47abe4e2abc49ad96f8a038cfe7f498" + integrity sha1-rd/4BNR6vk4qvEmtlvigOM/n9Jg= + +react-refresh@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" + integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg== + +react-scripts@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-4.0.1.tgz#34974c0f4cfdf1655906c95df6a04d80db8b88f0" + integrity sha512-NnniMSC/wjwhcJAyPJCWtxx6CWONqgvGgV9+QXj1bwoW/JI++YF1eEf3Upf/mQ9KmP57IBdjzWs1XvnPq7qMTQ== + dependencies: + "@babel/core" "7.12.3" + "@pmmmwh/react-refresh-webpack-plugin" "0.4.2" + "@svgr/webpack" "5.4.0" + "@typescript-eslint/eslint-plugin" "^4.5.0" + "@typescript-eslint/parser" "^4.5.0" + babel-eslint "^10.1.0" + babel-jest "^26.6.0" + babel-loader "8.1.0" + babel-plugin-named-asset-import "^0.3.7" + babel-preset-react-app "^10.0.0" + bfj "^7.0.2" + camelcase "^6.1.0" + case-sensitive-paths-webpack-plugin "2.3.0" + css-loader "4.3.0" + dotenv "8.2.0" + dotenv-expand "5.1.0" + eslint "^7.11.0" + eslint-config-react-app "^6.0.0" + eslint-plugin-flowtype "^5.2.0" + eslint-plugin-import "^2.22.1" + eslint-plugin-jest "^24.1.0" + eslint-plugin-jsx-a11y "^6.3.1" + eslint-plugin-react "^7.21.5" + eslint-plugin-react-hooks "^4.2.0" + eslint-plugin-testing-library "^3.9.2" + eslint-webpack-plugin "^2.1.0" + file-loader "6.1.1" + fs-extra "^9.0.1" + html-webpack-plugin "4.5.0" + identity-obj-proxy "3.0.0" + jest "26.6.0" + jest-circus "26.6.0" + jest-resolve "26.6.0" + jest-watch-typeahead "0.6.1" + mini-css-extract-plugin "0.11.3" + optimize-css-assets-webpack-plugin "5.0.4" + pnp-webpack-plugin "1.6.4" + postcss-flexbugs-fixes "4.2.1" + postcss-loader "3.0.0" + postcss-normalize "8.0.1" + postcss-preset-env "6.7.0" + postcss-safe-parser "5.0.2" + prompts "2.4.0" + react-app-polyfill "^2.0.0" + react-dev-utils "^11.0.1" + react-refresh "^0.8.3" + resolve "1.18.1" + resolve-url-loader "^3.1.2" + sass-loader "8.0.2" + semver "7.3.2" + style-loader "1.3.0" + terser-webpack-plugin "4.2.3" + ts-pnp "1.2.0" + url-loader "4.1.1" + webpack "4.44.2" + webpack-dev-server "3.11.0" + webpack-manifest-plugin "2.2.0" + workbox-webpack-plugin "5.1.4" + optionalDependencies: + fsevents "^2.1.3" + react-side-effect@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.1.tgz#66c5701c3e7560ab4822a4ee2742dee215d72eb3" @@ -6926,24 +11111,43 @@ react@^17.0.1: loose-envify "^1.1.0" object-assign "^4.1.1" -read-cache@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" - integrity sha1-5mTvMRYRZsl1HNvo28+GtftY93Q= +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= dependencies: - pify "^2.3.0" + find-up "^2.0.0" + read-pkg "^2.0.0" -"readable-stream@>=1.0.33-1 <1.1.0-0": - version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= +read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" -readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.3, readable-stream@~2.3.6: +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -6956,7 +11160,17 @@ readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.2.2, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.6.0: +"readable-stream@>=1.0.33-1 <1.1.0-0": + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -6981,13 +11195,20 @@ readdirp@~3.5.0: dependencies: picomatch "^2.2.1" -reduce-css-calc@^2.1.6: - version "2.1.7" - resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.7.tgz#1ace2e02c286d78abcd01fd92bfe8097ab0602c2" - integrity sha512-fDnlZ+AybAS3C7Q9xDq5y8A2z+lT63zLbynew/lur/IR24OQF5x98tfNwf79mzEdfywZ0a2wpM860FhFfMxZlA== +recursive-readdir@2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f" + integrity sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg== dependencies: - css-unit-converter "^1.1.1" - postcss-value-parser "^3.3.0" + minimatch "3.0.4" + +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" reduce-simplicial-complex@^1.0.0: version "1.0.0" @@ -7015,7 +11236,7 @@ regenerator-runtime@^0.11.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== -regenerator-runtime@^0.13.4: +regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: version "0.13.7" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== @@ -7035,11 +11256,29 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" +regex-parser@^2.2.11: + version "2.2.11" + resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.11.tgz#3b37ec9049e19479806e878cabe7c1ca83ccfe58" + integrity sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q== + regex-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/regex-regex/-/regex-regex-1.0.0.tgz#9048a1eaeb870f4d480dabc76fc42cdcc0bc3a72" integrity sha1-kEih6uuHD01IDavHb8Qs3MC8OnI= +regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" + integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +regexpp@^3.0.0, regexpp@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" + integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== + regexpu-core@^4.7.1: version "4.7.1" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" @@ -7078,11 +11317,12 @@ regl-error2d@^2.0.11: update-diff "^1.1.0" regl-line2d@^3.0.18: - version "3.0.18" - resolved "https://registry.yarnpkg.com/regl-line2d/-/regl-line2d-3.0.18.tgz#84b68dcf4d7f878f97554183de02175627f0c246" - integrity sha512-yX1TlV0SHBdn8EkU+9K+K19qx7WSDOchrKx+h43rE2NCWuPlVj/MPDgrIXnzhnd42XhQtvvnkSc7aCSLjGAhZQ== + version "3.1.0" + resolved "https://registry.yarnpkg.com/regl-line2d/-/regl-line2d-3.1.0.tgz#60a541e47a15387b85ca5c7abe73f7ef5038b3aa" + integrity sha512-8dB3SyAW5zTU759LrIJdkOe128htl1xlONHrknsFl1tAxZVqTc+WO/2k9pAJDuyiKu1v/6bosiuEDOB7G3dm4w== dependencies: array-bounds "^1.0.1" + array-find-index "^1.0.2" array-normalize "^1.1.4" color-normalize "^1.5.0" earcut "^2.1.5" @@ -7145,6 +11385,17 @@ remove-trailing-separator@^1.0.1: resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= +renderkid@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.4.tgz#d325e532afb28d3f8796ffee306be8ffd6fc864c" + integrity sha512-K2eXrSOJdq+HuKzlcjOlGoOarUu5SDguDEhE7+Ah4zuOWL40j8A/oHvLlLob9PSTNvVnBd+/q0Er1QfpEuem5g== + dependencies: + css-select "^1.1.0" + dom-converter "^0.2" + htmlparser2 "^3.3.0" + lodash "^4.17.20" + strip-ansi "^3.0.0" + repeat-element@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" @@ -7162,7 +11413,7 @@ request-promise-core@1.1.4: dependencies: lodash "^4.17.19" -request-promise-native@^1.0.5: +request-promise-native@^1.0.8: version "1.0.9" resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== @@ -7171,7 +11422,7 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.88.0: +request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -7202,6 +11453,30 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + +resolve-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" + integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= + dependencies: + resolve-from "^3.0.0" + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" @@ -7224,17 +11499,41 @@ resolve-protobuf-schema@^2.1.0: dependencies: protocol-buffers-schema "^3.3.1" +resolve-url-loader@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.2.tgz#235e2c28e22e3e432ba7a5d4e305c59a58edfc08" + integrity sha512-QEb4A76c8Mi7I3xNKXlRKQSlLBwjUV/ULFMP+G7n3/7tJZ8MG5wsZ3ucxP1Jz8Vevn6fnJsxDx9cIls+utGzPQ== + dependencies: + adjust-sourcemap-loader "3.0.0" + camelcase "5.3.1" + compose-function "3.0.3" + convert-source-map "1.7.0" + es6-iterator "2.0.3" + loader-utils "1.2.3" + postcss "7.0.21" + rework "1.0.1" + rework-visit "1.0.0" + source-map "0.6.1" + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= +resolve@1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130" + integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA== + dependencies: + is-core-module "^2.0.0" + path-parse "^1.0.6" + resolve@^0.6.1: version "0.6.3" resolved "https://registry.yarnpkg.com/resolve/-/resolve-0.6.3.tgz#dd957982e7e736debdf53b58a4dd91754575dd46" integrity sha1-3ZV5gufnNt699TtYpN2RdUV13UY= -resolve@^1.0.0, resolve@^1.1.5, resolve@^1.19.0, resolve@^1.4.0: +resolve@^1.0.0, resolve@^1.1.5, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.3.2, resolve@^1.8.1: version "1.19.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== @@ -7242,24 +11541,34 @@ resolve@^1.0.0, resolve@^1.1.5, resolve@^1.19.0, resolve@^1.4.0: is-core-module "^2.1.0" path-parse "^1.0.6" -restore-cursor@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" - integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= - dependencies: - onetime "^2.0.0" - signal-exit "^3.0.2" - ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rework-visit@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rework-visit/-/rework-visit-1.0.0.tgz#9945b2803f219e2f7aca00adb8bc9f640f842c9a" + integrity sha1-mUWygD8hni96ygCtuLyfZA+ELJo= + +rework@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rework/-/rework-1.0.1.tgz#30806a841342b54510aa4110850cd48534144aa7" + integrity sha1-MIBqhBNCtUUQqkEQhQzUhTQUSqc= + dependencies: + convert-source-map "^0.3.3" + css "^2.0.0" + rgb-regex@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" @@ -7275,13 +11584,27 @@ right-now@^1.0.0: resolved "https://registry.yarnpkg.com/right-now/-/right-now-1.0.0.tgz#6e89609deebd7dcdaf8daecc9aea39cf585a0918" integrity sha1-bolgne69fc2vja7Mmuo5z1haCRg= -rimraf@^2.6.2: +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +rimraf@^2.5.4, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: glob "^7.1.3" +rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -7373,26 +11696,73 @@ robust-sum@^1.0.0: resolved "https://registry.yarnpkg.com/robust-sum/-/robust-sum-1.0.0.tgz#16646e525292b4d25d82757a286955e0bbfa53d9" integrity sha1-FmRuUlKStNJdgnV6KGlV4Lv6U9k= +rollup-plugin-babel@^4.3.3: + version "4.4.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-babel/-/rollup-plugin-babel-4.4.0.tgz#d15bd259466a9d1accbdb2fe2fff17c52d030acb" + integrity sha512-Lek/TYp1+7g7I+uMfJnnSJ7YWoD58ajo6Oarhlex7lvUce+RCKRuGRSgztDO3/MF/PuGKmUL5iTHKf208UNszw== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + rollup-pluginutils "^2.8.1" + +rollup-plugin-terser@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-5.3.1.tgz#8c650062c22a8426c64268548957463bf981b413" + integrity sha512-1pkwkervMJQGFYvM9nscrUoncPwiKR/K+bHdjv6PFgRo3cgPHoRT83y2Aa3GvINj4539S15t/tpFPb775TDs6w== + dependencies: + "@babel/code-frame" "^7.5.5" + jest-worker "^24.9.0" + rollup-pluginutils "^2.8.2" + serialize-javascript "^4.0.0" + terser "^4.6.2" + +rollup-pluginutils@^2.8.1, rollup-pluginutils@^2.8.2: + version "2.8.2" + resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" + integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== + dependencies: + estree-walker "^0.6.1" + +rollup@^1.31.1: + version "1.32.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.32.1.tgz#4480e52d9d9e2ae4b46ba0d9ddeaf3163940f9c4" + integrity sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A== + dependencies: + "@types/estree" "*" + "@types/node" "*" + acorn "^7.1.0" + +rsvp@^4.8.4: + version "4.8.5" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" + integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== + run-parallel@^1.1.9: version "1.1.10" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.10.tgz#60a51b2ae836636c81377df16cb107351bcd13ef" integrity sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw== +run-queue@^1.0.0, run-queue@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" + integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec= + dependencies: + aproba "^1.1.1" + rw@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q= -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" @@ -7410,17 +11780,48 @@ sane-topojson@^4.0.0: resolved "https://registry.yarnpkg.com/sane-topojson/-/sane-topojson-4.0.0.tgz#624cdb26fc6d9392c806897bfd1a393f29bb5308" integrity sha512-bJILrpBboQfabG3BNnHI2hZl52pbt80BE09u4WhnrmzuF2JbMKZdl62G5glXskJ46p+gxE2IzOwGj/awR4g8AA== +sane@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" + integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== + dependencies: + "@cnakazawa/watch" "^1.0.3" + anymatch "^2.0.0" + capture-exit "^2.0.0" + exec-sh "^0.3.2" + execa "^1.0.0" + fb-watchman "^2.0.0" + micromatch "^3.1.4" + minimist "^1.1.1" + walker "~1.0.5" + +sanitize.css@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/sanitize.css/-/sanitize.css-10.0.0.tgz#b5cb2547e96d8629a60947544665243b1dc3657a" + integrity sha512-vTxrZz4dX5W86M6oVWVdOVe72ZiPs41Oi7Z6Km4W5Turyz28mrXSJhhEBZoRtzJWIv3833WKVwLSDWWkEfupMg== + +sass-loader@8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-8.0.2.tgz#debecd8c3ce243c76454f2e8290482150380090d" + integrity sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ== + dependencies: + clone-deep "^4.0.1" + loader-utils "^1.2.3" + neo-async "^2.6.1" + schema-utils "^2.6.1" + semver "^6.3.0" + sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -saxes@^3.1.9: - version "3.1.11" - resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.11.tgz#d59d1fd332ec92ad98a2e0b2ee644702384b1c5b" - integrity sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g== +saxes@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== dependencies: - xmlchars "^2.1.1" + xmlchars "^2.2.0" scheduler@^0.20.1: version "0.20.1" @@ -7430,15 +11831,64 @@ scheduler@^0.20.1: loose-envify "^1.1.0" object-assign "^4.1.1" +schema-utils@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" + integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== + dependencies: + ajv "^6.1.0" + ajv-errors "^1.0.0" + ajv-keywords "^3.1.0" + +schema-utils@^2.6.1, schema-utils@^2.6.5, schema-utils@^2.7.0, schema-utils@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" + integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== + dependencies: + "@types/json-schema" "^7.0.5" + ajv "^6.12.4" + ajv-keywords "^3.5.2" + +schema-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef" + integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA== + dependencies: + "@types/json-schema" "^7.0.6" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= + +selfsigned@^1.10.7: + version "1.10.8" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.8.tgz#0d17208b7d12c33f8eac85c41835f27fc3d81a30" + integrity sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w== + dependencies: + node-forge "^0.10.0" + +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + semver@7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@^5.4.1, semver@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== +semver@7.3.2, semver@^7.2.1, semver@^7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + +semver@^6.0.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== send@0.17.1: version "0.17.1" @@ -7459,12 +11909,34 @@ send@0.17.1: range-parser "~1.2.1" statuses "~1.5.0" -serialize-to-js@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/serialize-to-js/-/serialize-to-js-3.1.1.tgz#b3e77d0568ee4a60bfe66287f991e104d3a1a4ac" - integrity sha512-F+NGU0UHMBO4Q965tjw7rvieNVjlH6Lqi2emq/Lc9LUURYJbiCzmpi4Cy1OOjjVPtxu0c+NE85LU6968Wko5ZA== +serialize-javascript@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" + integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== + dependencies: + randombytes "^2.1.0" -serve-static@^1.12.4: +serialize-javascript@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" + integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== + dependencies: + randombytes "^2.1.0" + +serve-index@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk= + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.14.1: version "1.14.1" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== @@ -7474,6 +11946,11 @@ serve-static@^1.12.4: parseurl "~1.3.3" send "0.17.1" +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" @@ -7489,6 +11966,11 @@ setimmediate@^1.0.4: resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + setprototypeof@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" @@ -7502,7 +11984,14 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -shallow-copy@0.0.1, shallow-copy@~0.0.1: +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shallow-copy@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/shallow-copy/-/shallow-copy-0.0.1.tgz#415f42702d73d810330292cc5ee86eae1a11a170" integrity sha1-QV9CcC1z2BAzApLMXuhurhoRoXA= @@ -7514,12 +12003,42 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= -signal-exit@^3.0.2: +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" + integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== + +shellwords@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== + +side-channel@^1.0.2, side-channel@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.3.tgz#cdc46b057550bbab63706210838df5d4c19519c3" + integrity sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g== + dependencies: + es-abstract "^1.18.0-next.0" + object-inspect "^1.8.0" + +signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== @@ -7583,6 +12102,11 @@ simplify-planar-graph@^2.0.1: robust-orientation "^1.0.1" simplicial-complex "^0.3.3" +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + slab-decomposition@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/slab-decomposition/-/slab-decomposition-1.0.2.tgz#1ded56754d408b10739f145103dfc61807f65134" @@ -7597,6 +12121,15 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -7627,7 +12160,7 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" -socket.io-client@~3: +socket.io-client@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-3.0.4.tgz#c0203419a9f71e1360ef92a31301e80260e94bb9" integrity sha512-qMvBuS+W9JIN2mkfAWDCxuIt+jpIKDf8C0604zEqx1JrPaPSS6cN0F3B2GYWC83TqBeVJXW66GFxWV3KD88n0Q== @@ -7650,7 +12183,40 @@ socket.io-parser@~4.0.1: component-emitter "~1.3.0" debug "~4.1.0" -source-map-resolve@^0.5.0: +sockjs-client@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5" + integrity sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g== + dependencies: + debug "^3.2.5" + eventsource "^1.0.7" + faye-websocket "~0.11.1" + inherits "^2.0.3" + json3 "^3.3.2" + url-parse "^1.4.3" + +sockjs@0.3.20: + version "0.3.20" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.20.tgz#b26a283ec562ef8b2687b44033a4eeceac75d855" + integrity sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA== + dependencies: + faye-websocket "^0.10.0" + uuid "^3.4.0" + websocket-driver "0.6.5" + +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= + dependencies: + is-plain-obj "^1.0.0" + +source-list-map@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" + integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== + +source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== @@ -7669,7 +12235,7 @@ source-map-resolve@^0.6.0: atob "^2.1.2" decode-uri-component "^0.2.0" -source-map-support@~0.5.10, source-map-support@~0.5.12: +source-map-support@^0.5.6, source-map-support@~0.5.12, source-map-support@~0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== @@ -7682,7 +12248,7 @@ source-map-url@^0.4.0: resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= -source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: +source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -7692,6 +12258,65 @@ source-map@^0.5.0, source-map@^0.5.6: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= +source-map@^0.7.3, source-map@~0.7.2: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + +sourcemap-codec@^1.4.4: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.6" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz#c80757383c28abf7296744998cbc106ae8b854ce" + integrity sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw== + +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + split-polygon@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/split-polygon/-/split-polygon-1.0.0.tgz#0eacc8a136a76b12a3d95256ea7da45db0c2d247" @@ -7717,11 +12342,6 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -srcset@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/srcset/-/srcset-3.0.0.tgz#8afd8b971362dfc129ae9c1a99b3897301ce6441" - integrity sha512-D59vF08Qzu/C4GAOXVgMTLfgryt5fyWo93FZyhEWANo0PokFz/iWdDe13mX3O5TRf6l8vMTqckAfR4zPiaH0yQ== - sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -7737,6 +12357,20 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" +ssri@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" + integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== + dependencies: + figgy-pudding "^3.5.1" + +ssri@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.0.tgz#79ca74e21f8ceaeddfcb4b90143c458b8d988808" + integrity sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA== + dependencies: + minipass "^3.1.1" + stable@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" @@ -7747,7 +12381,19 @@ stack-trace@0.0.9: resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.9.tgz#a8f6eaeca90674c333e7c43953f275b451510695" integrity sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU= -static-eval@^2.0.0, static-eval@^2.0.5: +stack-utils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277" + integrity sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw== + dependencies: + escape-string-regexp "^2.0.0" + +stackframe@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303" + integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA== + +static-eval@^2.0.5: version "2.1.0" resolved "https://registry.yarnpkg.com/static-eval/-/static-eval-2.1.0.tgz#a16dbe54522d7fa5ef1389129d813fd47b148014" integrity sha512-agtxZ/kWSsCkI5E4QifRwsaPs0P0JmZV6dkLz6ILYfFYQGn+5plctanRN+IC8dJRiFkyXHrwEE3W9Wmx67uDbw== @@ -7762,27 +12408,7 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -static-module@^2.2.0: - version "2.2.5" - resolved "https://registry.yarnpkg.com/static-module/-/static-module-2.2.5.tgz#bd40abceae33da6b7afb84a0e4329ff8852bfbbf" - integrity sha512-D8vv82E/Kpmz3TXHKG8PPsCPg+RAX6cbCOyvjM6x04qZtQ47EtJFVwRsdov3n5d6/6ynrOY9XB4JkaZwB2xoRQ== - dependencies: - concat-stream "~1.6.0" - convert-source-map "^1.5.1" - duplexer2 "~0.1.4" - escodegen "~1.9.0" - falafel "^2.1.0" - has "^1.0.1" - magic-string "^0.22.4" - merge-source-map "1.0.4" - object-inspect "~1.4.0" - quote-stream "~1.0.2" - readable-stream "~2.3.3" - shallow-copy "~0.0.1" - static-eval "^2.0.0" - through2 "~2.0.3" - -"statuses@>= 1.5.0 < 2", statuses@~1.5.0: +"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= @@ -7800,6 +12426,14 @@ stream-browserify@^2.0.1: inherits "~2.0.1" readable-stream "^2.0.2" +stream-each@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" + integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== + dependencies: + end-of-stream "^1.1.0" + stream-shift "^1.0.0" + stream-http@^2.7.2: version "2.8.3" resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" @@ -7816,6 +12450,24 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= + +string-length@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.1.tgz#4a973bf31ef77c4edbceadd6af2611996985f8a1" + integrity sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-natural-compare@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" + integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== + string-split-by@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/string-split-by/-/string-split-by-1.0.0.tgz#53895fb3397ebc60adab1f1e3a131f5372586812" @@ -7831,6 +12483,15 @@ string-to-arraybuffer@^1.0.0: atob-lite "^2.0.0" is-base64 "^0.1.0" +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + string-width@^4.1.0, string-width@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" @@ -7840,6 +12501,19 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" +string.prototype.matchall@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.3.tgz#24243399bc31b0a49d19e2b74171a15653ec996a" + integrity sha512-OBxYDA2ifZQ2e13cP82dWFMaCV9CGF8GzmN4fljBVw5O5wep0lu4gacm1OL6MjROoUnB8VbkWRThqkV2YFLNxw== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + has-symbols "^1.0.1" + internal-slot "^1.0.2" + regexp.prototype.flags "^1.3.0" + side-channel "^1.0.3" + string.prototype.trimend@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz#a22bd53cca5c7cf44d7c9d5c732118873d6cd18b" @@ -7875,32 +12549,89 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= +stringify-object@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" + integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== dependencies: - ansi-regex "^2.0.0" + get-own-enumerable-property-symbols "^3.0.0" + is-obj "^1.0.1" + is-regexp "^1.0.0" -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^6.0.0: +strip-ansi@6.0.0, strip-ansi@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== dependencies: ansi-regex "^5.0.0" +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-comments@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/strip-comments/-/strip-comments-1.0.2.tgz#82b9c45e7f05873bee53f37168af930aa368679d" + integrity sha512-kL97alc47hoyIQSV165tTt9rG5dn4w1dNnBhOQ3bOU1Nc1hel09jnXANaHJ7vzHLd4Ju8kseDGzlev96pghLFw== + dependencies: + babel-extract-comments "^1.0.0" + babel-plugin-transform-object-rest-spread "^6.26.0" + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + strongly-connected-components@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/strongly-connected-components/-/strongly-connected-components-1.0.1.tgz#0920e2b4df67c8eaee96c6b6234fe29e873dba99" integrity sha1-CSDitN9nyOrulsa2I0/inoc9upk= +style-loader@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.3.0.tgz#828b4a3b3b7e7aa5847ce7bae9e874512114249e" + integrity sha512-V7TCORko8rs9rIqkSrlMfkqA63DfoGBBJmK1kKGCcSi+BWb4cqz0SRsnp4l6rU5iwOEd0/2ePv68SV22VXon4Q== + dependencies: + loader-utils "^2.0.0" + schema-utils "^2.7.0" + stylehacks@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" @@ -7922,19 +12653,7 @@ superscript-text@^1.0.0: resolved "https://registry.yarnpkg.com/superscript-text/-/superscript-text-1.0.0.tgz#e7cb2752567360df50beb0610ce8df3d71d8dfd8" integrity sha1-58snUlZzYN9QvrBhDOjfPXHY39g= -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= - -supports-color@^3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" - integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= - dependencies: - has-flag "^1.0.0" - -supports-color@^5.3.0, supports-color@^5.4.0: +supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -7948,13 +12667,21 @@ supports-color@^6.1.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0: +supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" +supports-hyperlinks@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz#f663df252af5f37c5d49bbd7eeefa9e0b9e59e47" + integrity sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + surface-nets@^1.0.0, surface-nets@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/surface-nets/-/surface-nets-1.0.2.tgz#e433c8cbba94a7274c6f4c99552b461bf1fc7a4b" @@ -7969,6 +12696,11 @@ svg-arc-to-cubic-bezier@^3.0.0: resolved "https://registry.yarnpkg.com/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz#390c450035ae1c4a0104d90650304c3bc814abe6" integrity sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g== +svg-parser@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" + integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== + svg-path-bounds@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/svg-path-bounds/-/svg-path-bounds-1.0.1.tgz#bf458b783726bf53431b4633f2792f60748d9f74" @@ -7990,7 +12722,7 @@ svg-path-sdf@^1.1.3: parse-svg-path "^0.1.2" svg-path-bounds "^1.0.1" -svgo@^1.0.0, svgo@^1.3.2: +svgo@^1.0.0, svgo@^1.2.2: version "1.3.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== @@ -8009,47 +12741,91 @@ svgo@^1.0.0, svgo@^1.3.2: unquote "~1.1.1" util.promisify "~1.0.0" -symbol-tree@^3.2.2: +symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -tailwindcss@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-2.0.2.tgz#28e1573d29dd4547b26782facb05bcfaa92be366" - integrity sha512-nO9JRE1pO7SF9RnYAl6g7uzeHdrmKAFqNjT9NtZUfxqimJZAOOLOEyIEUiMq12+xIc7mC2Ey3Vf90XjHpWKfbw== +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== dependencies: - "@fullhuman/postcss-purgecss" "^3.0.0" - bytes "^3.0.0" - chalk "^4.1.0" - color "^3.1.3" - detective "^5.2.0" - didyoumean "^1.2.1" - fs-extra "^9.0.1" - html-tags "^3.1.0" - lodash "^4.17.20" - modern-normalize "^1.0.0" - node-emoji "^1.8.1" - object-hash "^2.0.3" - postcss-functions "^3" - postcss-js "^3.0.3" - postcss-nested "^5.0.1" - postcss-selector-parser "^6.0.4" - postcss-value-parser "^4.1.0" - pretty-hrtime "^1.0.3" - reduce-css-calc "^2.1.6" - resolve "^1.19.0" + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" -terser@^3.7.3: - version "3.17.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2" - integrity sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ== +tapable@^1.0.0, tapable@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + +tar@^6.0.2: + version "6.0.5" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f" + integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg== dependencies: - commander "^2.19.0" - source-map "~0.6.1" - source-map-support "~0.5.10" + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" -terser@^4.8.0: +temp-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" + integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0= + +tempy@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/tempy/-/tempy-0.3.0.tgz#6f6c5b295695a16130996ad5ab01a8bd726e8bf8" + integrity sha512-WrH/pui8YCwmeiAoxV+lpRH9HpRtgBhSR2ViBPgpGb/wnYDzp21R4MN45fsCGvLROvY67o3byhJRYRONJyImVQ== + dependencies: + temp-dir "^1.0.0" + type-fest "^0.3.1" + unique-string "^1.0.0" + +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +terser-webpack-plugin@4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz#28daef4a83bd17c1db0297070adc07fc8cfc6a9a" + integrity sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ== + dependencies: + cacache "^15.0.5" + find-cache-dir "^3.3.1" + jest-worker "^26.5.0" + p-limit "^3.0.2" + schema-utils "^3.0.0" + serialize-javascript "^5.0.1" + source-map "^0.6.1" + terser "^5.3.4" + webpack-sources "^1.4.3" + +terser-webpack-plugin@^1.4.3: + version "1.4.5" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz#a217aefaea330e734ffacb6120ec1fa312d6040b" + integrity sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw== + dependencies: + cacache "^12.0.2" + find-cache-dir "^2.1.0" + is-wsl "^1.1.0" + schema-utils "^1.0.0" + serialize-javascript "^4.0.0" + source-map "^0.6.1" + terser "^4.1.2" + webpack-sources "^1.4.0" + worker-farm "^1.7.0" + +terser@^4.1.2, terser@^4.6.2, terser@^4.6.3: version "4.8.0" resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== @@ -8058,6 +12834,24 @@ terser@^4.8.0: source-map "~0.6.1" source-map-support "~0.5.12" +terser@^5.3.4: + version "5.5.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.5.0.tgz#1406fcb4d4bc517add3b22a9694284c040e33448" + integrity sha512-eopt1Gf7/AQyPhpygdKePTzaet31TvQxXvrf7xYUvD/d8qkCJm4SKPDzu+GHK5ZaYTn8rvttfqaZc3swK21e5g== + dependencies: + commander "^2.20.0" + source-map "~0.7.2" + source-map-support "~0.5.19" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + text-cache@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/text-cache/-/text-cache-4.2.2.tgz#d0d30ba89b7312ea1c1a31cd9a4db56c1cef7fe7" @@ -8065,6 +12859,16 @@ text-cache@^4.2.2: dependencies: vectorize-text "^3.2.1" +text-table@0.2.0, text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +throat@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" + integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== + through2@^0.6.3: version "0.6.5" resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" @@ -8073,7 +12877,7 @@ through2@^0.6.3: readable-stream ">=1.0.33-1 <1.1.0-0" xtend ">=4.0.0 <4.1.0-0" -through2@^2.0.0, through2@^2.0.1, through2@~2.0.3: +through2@^2.0.0, through2@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== @@ -8081,6 +12885,11 @@ through2@^2.0.0, through2@^2.0.1, through2@~2.0.3: readable-stream "~2.3.6" xtend "~4.0.1" +thunky@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" + integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== + timers-browserify@^2.0.4: version "2.0.12" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" @@ -8093,11 +12902,6 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= -tiny-inflate@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" - integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== - tinycolor2@^1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" @@ -8108,6 +12912,11 @@ tinyqueue@^2.0.3: resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08" integrity sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA== +tmpl@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= + to-array-buffer@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/to-array-buffer/-/to-array-buffer-3.2.0.tgz#cb684dd691a7368c3b249c2348d75227f7d4dbb4" @@ -8122,11 +12931,6 @@ to-arraybuffer@^1.0.0: resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= -to-fast-properties@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" - integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= - to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -8206,7 +13010,7 @@ topojson-client@^3.1.0: dependencies: commander "2" -tough-cookie@^2.3.3, tough-cookie@^2.5.0, tough-cookie@~2.5.0: +tough-cookie@^2.3.3, tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== @@ -8214,12 +13018,21 @@ tough-cookie@^2.3.3, tough-cookie@^2.5.0, tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" -tr46@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" - integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= +tough-cookie@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" + integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== dependencies: - punycode "^2.1.0" + ip-regex "^2.1.0" + psl "^1.1.28" + punycode "^2.1.1" + +tr46@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.0.2.tgz#03273586def1595ae08fedb38d7733cee91d2479" + integrity sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg== + dependencies: + punycode "^2.1.1" triangulate-hypercube@^1.0.0: version "1.0.1" @@ -8237,6 +13050,38 @@ triangulate-polyline@^1.0.0: dependencies: cdt2d "^1.0.0" +tryer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" + integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA== + +ts-pnp@1.2.0, ts-pnp@^1.1.6: + version "1.2.0" + resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" + integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== + +tsconfig-paths@^3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" + integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + +tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tsutils@^3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" + integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== + dependencies: + tslib "^1.8.1" + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" @@ -8273,6 +13118,13 @@ two-sum@^1.0.0: resolved "https://registry.yarnpkg.com/two-sum/-/two-sum-1.0.0.tgz#31d3f32239e4f731eca9df9155e2b297f008ab64" integrity sha1-MdPzIjnk9zHsqd+RVeKyl/AIq2Q= +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" @@ -8280,6 +13132,39 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" + integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== + +type-fest@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1" + integrity sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ== + +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +type-is@~1.6.17, type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + type-name@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/type-name/-/type-name-2.0.2.tgz#efe7d4123d8ac52afff7f40c7e4dec5266008fb4" @@ -8303,31 +13188,23 @@ typedarray-pool@^1.0.0, typedarray-pool@^1.0.2, typedarray-pool@^1.1.0, typedarr bit-twiddle "^1.0.0" dup "^1.0.0" +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^4.1.2: +typescript@^4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== -uncss@^0.17.3: - version "0.17.3" - resolved "https://registry.yarnpkg.com/uncss/-/uncss-0.17.3.tgz#50fc1eb4ed573ffff763458d801cd86e4d69ea11" - integrity sha512-ksdDWl81YWvF/X14fOSw4iu8tESDHFIeyKIeDrK6GEVTQvqJc1WlOEXqostNwOCi3qAj++4EaLsdAgPmUbEyog== - dependencies: - commander "^2.20.0" - glob "^7.1.4" - is-absolute-url "^3.0.1" - is-html "^1.1.0" - jsdom "^14.1.0" - lodash "^4.17.15" - postcss "^7.0.17" - postcss-selector-parser "6.0.2" - request "^2.88.0" - unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -8351,14 +13228,6 @@ unicode-property-aliases-ecmascript@^1.0.4: resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== -unicode-trie@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-0.3.1.tgz#d671dddd89101a08bac37b6a5161010602052085" - integrity sha1-1nHd3YkQGgi6w3tqUWEBBgIFIIU= - dependencies: - pako "^0.2.5" - tiny-inflate "^1.0.0" - union-find@^1.0.0, union-find@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/union-find/-/union-find-1.0.2.tgz#292bac415e6ad3a89535d237010db4a536284e58" @@ -8389,6 +13258,32 @@ uniqs@^2.0.0: resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + +unique-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" + integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo= + dependencies: + crypto-random-string "^1.0.0" + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + universalify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" @@ -8399,6 +13294,11 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + unquote@^1.1.0, unquote@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" @@ -8412,7 +13312,7 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -upath@^1.1.1: +upath@^1.1.1, upath@^1.1.2, upath@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== @@ -8434,6 +13334,23 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= +url-loader@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2" + integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA== + dependencies: + loader-utils "^2.0.0" + mime-types "^2.1.27" + schema-utils "^3.0.0" + +url-parse@^1.4.3: + version "1.4.7" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" + integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -8452,6 +13369,14 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= +util.promisify@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" + integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== + dependencies: + define-properties "^1.1.2" + object.getownpropertydescriptors "^2.0.3" + util.promisify@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" @@ -8476,6 +13401,11 @@ util@^0.11.0: dependencies: inherits "2.0.3" +utila@~0.4: + version "0.4.0" + resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" + integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= + utils-copy-error@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-copy-error/-/utils-copy-error-1.0.1.tgz#791de393c0f09890afd59f3cbea635f079a94fa5" @@ -8507,6 +13437,11 @@ utils-indexof@^1.0.0: validate.io-array-like "^1.0.1" validate.io-integer-primitive "^1.0.0" +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + utils-regex-from-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/utils-regex-from-string/-/utils-regex-from-string-1.0.0.tgz#fe1a2909f8de0ff0d5182c80fbc654d6a687d189" @@ -8515,16 +13450,38 @@ utils-regex-from-string@^1.0.0: regex-regex "^1.0.0" validate.io-string-primitive "^1.0.0" -uuid@^3.3.2: +uuid@^3.3.2, uuid@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -v8-compile-cache@^2.0.0: +uuid@^8.3.0: + version "8.3.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31" + integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg== + +v8-compile-cache@^2.0.3: version "2.2.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== +v8-to-istanbul@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.0.0.tgz#b4fe00e35649ef7785a9b7fcebcea05f37c332fc" + integrity sha512-fLL2rFuQpMtm9r8hrAV2apXX/WqHJ6+IC4/eQVdMDGBUgH/YMV4Gv3duk3kjmyg6uiQWBAA9nJwue4iJUOkHeA== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + source-map "^0.7.3" + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + validate.io-array-like@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/validate.io-array-like/-/validate.io-array-like-1.0.2.tgz#7af9f7eb7b51715beb2215668ec5cce54faddb5a" @@ -8596,6 +13553,11 @@ validate.io-string-primitive@^1.0.0: resolved "https://registry.yarnpkg.com/validate.io-string-primitive/-/validate.io-string-primitive-1.0.1.tgz#b8135b9fb1372bde02fdd53ad1d0ccd6de798fee" integrity sha1-uBNbn7E3K94C/dU60dDM1t55j+4= +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + vectorize-text@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/vectorize-text/-/vectorize-text-3.2.1.tgz#85921abd9685af775fd20a01041a2837fe51bdb5" @@ -8623,10 +13585,10 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -vlq@^0.2.2: - version "0.2.3" - resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26" - integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow== +vfile-location@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.2.0.tgz#d8e41fbcbd406063669ebf6c33d56ae8721d0f3c" + integrity sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA== vm-browserify@^1.0.1: version "1.1.2" @@ -8642,28 +13604,51 @@ vt-pbf@^3.1.1: "@mapbox/vector-tile" "^1.3.1" pbf "^3.0.5" -w3c-hr-time@^1.0.1: +w3c-hr-time@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== dependencies: browser-process-hrtime "^1.0.0" -w3c-xmlserializer@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794" - integrity sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg== +w3c-xmlserializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" + integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== dependencies: - domexception "^1.0.1" - webidl-conversions "^4.0.2" xml-name-validator "^3.0.0" -wcwidth@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" - integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= +walker@^1.0.7, walker@~1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= dependencies: - defaults "^1.0.3" + makeerror "1.0.x" + +watchpack-chokidar2@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957" + integrity sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww== + dependencies: + chokidar "^2.1.8" + +watchpack@^1.7.4: + version "1.7.5" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453" + integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== + dependencies: + graceful-fs "^4.1.2" + neo-async "^2.5.0" + optionalDependencies: + chokidar "^3.4.1" + watchpack-chokidar2 "^2.0.1" + +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" weak-map@^1.0.5: version "1.0.5" @@ -8675,6 +13660,11 @@ weakmap-shim@^1.1.0: resolved "https://registry.yarnpkg.com/weakmap-shim/-/weakmap-shim-1.1.1.tgz#d65afd784109b2166e00ff571c33150ec2a40b49" integrity sha1-1lr9eEEJshZuAP9XHDMVDsKkC0k= +web-vitals@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-0.2.4.tgz#ec3df43c834a207fd7cdefd732b2987896e08511" + integrity sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg== + webgl-context@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/webgl-context/-/webgl-context-2.2.0.tgz#8f37d7257cf6df1cd0a49e6a7b1b721b94cc86a0" @@ -8682,44 +13672,359 @@ webgl-context@^2.2.0: dependencies: get-canvas-context "^1.0.1" -webidl-conversions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" - integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== +webidl-conversions@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== -whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: +webidl-conversions@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== + +webpack-dev-middleware@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" + integrity sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw== + dependencies: + memory-fs "^0.4.1" + mime "^2.4.4" + mkdirp "^0.5.1" + range-parser "^1.2.1" + webpack-log "^2.0.0" + +webpack-dev-server@3.11.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz#8f154a3bce1bcfd1cc618ef4e703278855e7ff8c" + integrity sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg== + dependencies: + ansi-html "0.0.7" + bonjour "^3.5.0" + chokidar "^2.1.8" + compression "^1.7.4" + connect-history-api-fallback "^1.6.0" + debug "^4.1.1" + del "^4.1.1" + express "^4.17.1" + html-entities "^1.3.1" + http-proxy-middleware "0.19.1" + import-local "^2.0.0" + internal-ip "^4.3.0" + ip "^1.1.5" + is-absolute-url "^3.0.3" + killable "^1.0.1" + loglevel "^1.6.8" + opn "^5.5.0" + p-retry "^3.0.1" + portfinder "^1.0.26" + schema-utils "^1.0.0" + selfsigned "^1.10.7" + semver "^6.3.0" + serve-index "^1.9.1" + sockjs "0.3.20" + sockjs-client "1.4.0" + spdy "^4.0.2" + strip-ansi "^3.0.1" + supports-color "^6.1.0" + url "^0.11.0" + webpack-dev-middleware "^3.7.2" + webpack-log "^2.0.0" + ws "^6.2.1" + yargs "^13.3.2" + +webpack-log@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f" + integrity sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg== + dependencies: + ansi-colors "^3.0.0" + uuid "^3.3.2" + +webpack-manifest-plugin@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-2.2.0.tgz#19ca69b435b0baec7e29fbe90fb4015de2de4f16" + integrity sha512-9S6YyKKKh/Oz/eryM1RyLVDVmy3NSPV0JXMRhZ18fJsq+AwGxUY34X54VNwkzYcEmEkDwNxuEOboCZEebJXBAQ== + dependencies: + fs-extra "^7.0.0" + lodash ">=3.5 <5" + object.entries "^1.1.0" + tapable "^1.0.0" + +webpack-sources@^1.1.0, webpack-sources@^1.3.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" + integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== + dependencies: + source-list-map "^2.0.0" + source-map "~0.6.1" + +webpack@4.44.2: + version "4.44.2" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.44.2.tgz#6bfe2b0af055c8b2d1e90ed2cd9363f841266b72" + integrity sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-module-context" "1.9.0" + "@webassemblyjs/wasm-edit" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + acorn "^6.4.1" + ajv "^6.10.2" + ajv-keywords "^3.4.1" + chrome-trace-event "^1.0.2" + enhanced-resolve "^4.3.0" + eslint-scope "^4.0.3" + json-parse-better-errors "^1.0.2" + loader-runner "^2.4.0" + loader-utils "^1.2.3" + memory-fs "^0.4.1" + micromatch "^3.1.10" + mkdirp "^0.5.3" + neo-async "^2.6.1" + node-libs-browser "^2.2.1" + schema-utils "^1.0.0" + tapable "^1.1.3" + terser-webpack-plugin "^1.4.3" + watchpack "^1.7.4" + webpack-sources "^1.4.1" + +websocket-driver@0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36" + integrity sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY= + dependencies: + websocket-extensions ">=0.1.1" + +websocket-driver@>=0.5.1: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== dependencies: iconv-lite "0.4.24" -whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: +whatwg-fetch@^3.4.1: + version "3.5.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz#605a2cd0a7146e5db141e29d1c62ab84c0c4c868" + integrity sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A== + +whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== -whatwg-url@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" - integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== +whatwg-url@^8.0.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.4.0.tgz#50fb9615b05469591d2b2bd6dfaed2942ed72837" + integrity sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw== dependencies: lodash.sortby "^4.7.0" - tr46 "^1.0.1" - webidl-conversions "^4.0.2" + tr46 "^2.0.2" + webidl-conversions "^6.1.0" -which@^1.2.9: +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" -word-wrap@~1.2.3: +which@^2.0.1, which@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== +workbox-background-sync@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-5.1.4.tgz#5ae0bbd455f4e9c319e8d827c055bb86c894fd12" + integrity sha512-AH6x5pYq4vwQvfRDWH+vfOePfPIYQ00nCEB7dJRU1e0n9+9HMRyvI63FlDvtFT2AvXVRsXvUt7DNMEToyJLpSA== + dependencies: + workbox-core "^5.1.4" + +workbox-broadcast-update@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-5.1.4.tgz#0eeb89170ddca7f6914fa3523fb14462891f2cfc" + integrity sha512-HTyTWkqXvHRuqY73XrwvXPud/FN6x3ROzkfFPsRjtw/kGZuZkPzfeH531qdUGfhtwjmtO/ZzXcWErqVzJNdXaA== + dependencies: + workbox-core "^5.1.4" + +workbox-build@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-5.1.4.tgz#23d17ed5c32060c363030c8823b39d0eabf4c8c7" + integrity sha512-xUcZn6SYU8usjOlfLb9Y2/f86Gdo+fy1fXgH8tJHjxgpo53VVsqRX0lUDw8/JuyzNmXuo8vXX14pXX2oIm9Bow== + dependencies: + "@babel/core" "^7.8.4" + "@babel/preset-env" "^7.8.4" + "@babel/runtime" "^7.8.4" + "@hapi/joi" "^15.1.0" + "@rollup/plugin-node-resolve" "^7.1.1" + "@rollup/plugin-replace" "^2.3.1" + "@surma/rollup-plugin-off-main-thread" "^1.1.1" + common-tags "^1.8.0" + fast-json-stable-stringify "^2.1.0" + fs-extra "^8.1.0" + glob "^7.1.6" + lodash.template "^4.5.0" + pretty-bytes "^5.3.0" + rollup "^1.31.1" + rollup-plugin-babel "^4.3.3" + rollup-plugin-terser "^5.3.1" + source-map "^0.7.3" + source-map-url "^0.4.0" + stringify-object "^3.3.0" + strip-comments "^1.0.2" + tempy "^0.3.0" + upath "^1.2.0" + workbox-background-sync "^5.1.4" + workbox-broadcast-update "^5.1.4" + workbox-cacheable-response "^5.1.4" + workbox-core "^5.1.4" + workbox-expiration "^5.1.4" + workbox-google-analytics "^5.1.4" + workbox-navigation-preload "^5.1.4" + workbox-precaching "^5.1.4" + workbox-range-requests "^5.1.4" + workbox-routing "^5.1.4" + workbox-strategies "^5.1.4" + workbox-streams "^5.1.4" + workbox-sw "^5.1.4" + workbox-window "^5.1.4" + +workbox-cacheable-response@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-5.1.4.tgz#9ff26e1366214bdd05cf5a43da9305b274078a54" + integrity sha512-0bfvMZs0Of1S5cdswfQK0BXt6ulU5kVD4lwer2CeI+03czHprXR3V4Y8lPTooamn7eHP8Iywi5QjyAMjw0qauA== + dependencies: + workbox-core "^5.1.4" + +workbox-core@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-5.1.4.tgz#8bbfb2362ecdff30e25d123c82c79ac65d9264f4" + integrity sha512-+4iRQan/1D8I81nR2L5vcbaaFskZC2CL17TLbvWVzQ4qiF/ytOGF6XeV54pVxAvKUtkLANhk8TyIUMtiMw2oDg== + +workbox-expiration@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-5.1.4.tgz#92b5df461e8126114943a3b15c55e4ecb920b163" + integrity sha512-oDO/5iC65h2Eq7jctAv858W2+CeRW5e0jZBMNRXpzp0ZPvuT6GblUiHnAsC5W5lANs1QS9atVOm4ifrBiYY7AQ== + dependencies: + workbox-core "^5.1.4" + +workbox-google-analytics@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-5.1.4.tgz#b3376806b1ac7d7df8418304d379707195fa8517" + integrity sha512-0IFhKoEVrreHpKgcOoddV+oIaVXBFKXUzJVBI+nb0bxmcwYuZMdteBTp8AEDJacENtc9xbR0wa9RDCnYsCDLjA== + dependencies: + workbox-background-sync "^5.1.4" + workbox-core "^5.1.4" + workbox-routing "^5.1.4" + workbox-strategies "^5.1.4" + +workbox-navigation-preload@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-5.1.4.tgz#30d1b720d26a05efc5fa11503e5cc1ed5a78902a" + integrity sha512-Wf03osvK0wTflAfKXba//QmWC5BIaIZARU03JIhAEO2wSB2BDROWI8Q/zmianf54kdV7e1eLaIEZhth4K4MyfQ== + dependencies: + workbox-core "^5.1.4" + +workbox-precaching@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-5.1.4.tgz#874f7ebdd750dd3e04249efae9a1b3f48285fe6b" + integrity sha512-gCIFrBXmVQLFwvAzuGLCmkUYGVhBb7D1k/IL7pUJUO5xacjLcFUaLnnsoVepBGAiKw34HU1y/YuqvTKim9qAZA== + dependencies: + workbox-core "^5.1.4" + +workbox-range-requests@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-5.1.4.tgz#7066a12c121df65bf76fdf2b0868016aa2bab859" + integrity sha512-1HSujLjgTeoxHrMR2muDW2dKdxqCGMc1KbeyGcmjZZAizJTFwu7CWLDmLv6O1ceWYrhfuLFJO+umYMddk2XMhw== + dependencies: + workbox-core "^5.1.4" + +workbox-routing@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-5.1.4.tgz#3e8cd86bd3b6573488d1a2ce7385e547b547e970" + integrity sha512-8ljknRfqE1vEQtnMtzfksL+UXO822jJlHTIR7+BtJuxQ17+WPZfsHqvk1ynR/v0EHik4x2+826Hkwpgh4GKDCw== + dependencies: + workbox-core "^5.1.4" + +workbox-strategies@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-5.1.4.tgz#96b1418ccdfde5354612914964074d466c52d08c" + integrity sha512-VVS57LpaJTdjW3RgZvPwX0NlhNmscR7OQ9bP+N/34cYMDzXLyA6kqWffP6QKXSkca1OFo/v6v7hW7zrrguo6EA== + dependencies: + workbox-core "^5.1.4" + workbox-routing "^5.1.4" + +workbox-streams@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-5.1.4.tgz#05754e5e3667bdc078df2c9315b3f41210d8cac0" + integrity sha512-xU8yuF1hI/XcVhJUAfbQLa1guQUhdLMPQJkdT0kn6HP5CwiPOGiXnSFq80rAG4b1kJUChQQIGPrq439FQUNVrw== + dependencies: + workbox-core "^5.1.4" + workbox-routing "^5.1.4" + +workbox-sw@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-5.1.4.tgz#2bb34c9f7381f90d84cef644816d45150011d3db" + integrity sha512-9xKnKw95aXwSNc8kk8gki4HU0g0W6KXu+xks7wFuC7h0sembFnTrKtckqZxbSod41TDaGh+gWUA5IRXrL0ECRA== + +workbox-webpack-plugin@5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-5.1.4.tgz#7bfe8c16e40fe9ed8937080ac7ae9c8bde01e79c" + integrity sha512-PZafF4HpugZndqISi3rZ4ZK4A4DxO8rAqt2FwRptgsDx7NF8TVKP86/huHquUsRjMGQllsNdn4FNl8CD/UvKmQ== + dependencies: + "@babel/runtime" "^7.5.5" + fast-json-stable-stringify "^2.0.0" + source-map-url "^0.4.0" + upath "^1.1.2" + webpack-sources "^1.3.0" + workbox-build "^5.1.4" + +workbox-window@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-5.1.4.tgz#2740f7dea7f93b99326179a62f1cc0ca2c93c863" + integrity sha512-vXQtgTeMCUq/4pBWMfQX8Ee7N2wVC4Q7XYFqLnfbXJ2hqew/cU1uMTD2KqGEgEpE4/30luxIxgE+LkIa8glBYw== + dependencies: + workbox-core "^5.1.4" + +worker-farm@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" + integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== + dependencies: + errno "~0.1.7" + +worker-rpc@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/worker-rpc/-/worker-rpc-0.1.1.tgz#cb565bd6d7071a8f16660686051e969ad32f54d5" + integrity sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg== + dependencies: + microevent.ts "~0.1.1" + world-calendars@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/world-calendars/-/world-calendars-1.0.3.tgz#b25c5032ba24128ffc41d09faf4a5ec1b9c14335" @@ -8727,10 +14032,19 @@ world-calendars@^1.0.3: dependencies: object-assign "^4.1.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== dependencies: ansi-styles "^4.0.0" string-width "^4.1.0" @@ -8741,20 +14055,35 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@^5.1.1: - version "5.2.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" - integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA== +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== dependencies: - async-limiter "~1.0.0" + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" -ws@^6.1.2: +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" + +ws@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== dependencies: async-limiter "~1.0.0" +ws@^7.2.3: + version "7.4.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.0.tgz#a5dd76a24197940d4a8bb9e0e152bb4503764da7" + integrity sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ== + ws@~7.2.1: version "7.2.5" resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.5.tgz#abb1370d4626a5a9cd79d8de404aa18b3465d10d" @@ -8765,7 +14094,7 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== -xmlchars@^2.1.1: +xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== @@ -8775,7 +14104,7 @@ xmlhttprequest-ssl@~1.5.4: resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= -"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.1: +"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== @@ -8785,33 +14114,74 @@ xtend@^2.1.2: resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.2.0.tgz#eef6b1f198c1c8deafad8b1765a04dad4a01c5a9" integrity sha1-7vax8ZjByN6vrYsXZaBNrUoBxak= -y18n@^5.0.5: - version "5.0.5" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.5.tgz#8769ec08d03b1ea2df2500acef561743bbb9ab18" - integrity sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg== +y18n@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== -yaml@^1.10.0: +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.10.0, yaml@^1.7.2: version "1.10.0" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== -yargs-parser@^20.2.2: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - -yargs@^16.0.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== +yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs@^13.3.2: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== + dependencies: + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.1.2" + +yargs@^15.4.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" yeast@0.1.2: version "0.1.2" -- 2.47.2 From 7b0228c014b08e322faa4dc722cdac4edbc43185 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 30 Dec 2020 13:18:54 +0000 Subject: [PATCH 002/127] await account info --- bfxbot/bfxbot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bfxbot/bfxbot.py b/bfxbot/bfxbot.py index 49eda47..5296035 100644 --- a/bfxbot/bfxbot.py +++ b/bfxbot/bfxbot.py @@ -125,7 +125,7 @@ class BfxBot: self.__status[symbol] = SymbolStatus(symbol, strategy) async def start(self): - self.__account_info = self.__bfx.get_account_information() + self.__account_info = await self.__bfx.get_account_information() self.__ledger = await self.__bfx.ledger_history(0, time.time() * 1000) await self.__update_status__() -- 2.47.2 From d67fc3b2df171ad639a058eb698b537d0bf62b2e Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 30 Dec 2020 15:33:10 +0000 Subject: [PATCH 003/127] removed python source --- bfxbot.iml | 12 -- bfxbot/__init__.py | 1 - bfxbot/bfxbot.py | 151 ---------------------- bfxbot/bfxwrapper.py | 212 ------------------------------- bfxbot/currency.py | 161 ----------------------- bfxbot/models.py | 295 ------------------------------------------- bfxbot/utils.py | 59 --------- main.py | 140 -------------------- requirements.txt | 36 ------ sounds/1up.mp3 | Bin 56106 -> 0 bytes sounds/coin.mp3 | Bin 48444 -> 0 bytes sounds/gameover.mp3 | Bin 126456 -> 0 bytes sounds/goal.wav | Bin 127638 -> 0 bytes static/.gitignore | 3 - strategy.py | 123 ------------------ templates/index.html | 16 --- websrc/.eslintcache | 2 +- 17 files changed, 1 insertion(+), 1210 deletions(-) delete mode 100644 bfxbot.iml delete mode 100644 bfxbot/__init__.py delete mode 100644 bfxbot/bfxbot.py delete mode 100644 bfxbot/bfxwrapper.py delete mode 100644 bfxbot/currency.py delete mode 100644 bfxbot/models.py delete mode 100644 bfxbot/utils.py delete mode 100755 main.py delete mode 100644 requirements.txt delete mode 100644 sounds/1up.mp3 delete mode 100644 sounds/coin.mp3 delete mode 100644 sounds/gameover.mp3 delete mode 100644 sounds/goal.wav delete mode 100644 static/.gitignore delete mode 100644 strategy.py delete mode 100644 templates/index.html diff --git a/bfxbot.iml b/bfxbot.iml deleted file mode 100644 index 629892c..0000000 --- a/bfxbot.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/bfxbot/__init__.py b/bfxbot/__init__.py deleted file mode 100644 index f16da45..0000000 --- a/bfxbot/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .bfxbot import BfxBot diff --git a/bfxbot/bfxbot.py b/bfxbot/bfxbot.py deleted file mode 100644 index 5296035..0000000 --- a/bfxbot/bfxbot.py +++ /dev/null @@ -1,151 +0,0 @@ -import asyncio -import time -from typing import Dict, List, Optional, Tuple - -from bfxapi import Order - -from bfxbot.bfxwrapper import BfxWrapper -from bfxbot.currency import TradingPair, Symbol -from bfxbot.models import SymbolStatus, Ticker, EventHandler, Strategy, Event, EventKind, OFFER_PERC, PositionWrapper - - -class BfxBot: - def __init__(self, api_key: str, api_secret: str, symbols: List[TradingPair], quote: Symbol, - tick_duration: int = 1): - if api_key is None: - print("API_KEY is not set!") - raise ValueError - - if api_secret is None: - print("API_SECRET is not set!") - raise ValueError - - self.__bfx: BfxWrapper = BfxWrapper(api_key, api_secret) - self.__ticker: Ticker = Ticker(tick_duration) - self.__status: Dict[TradingPair, SymbolStatus] = {} - self.__quote: Symbol = quote - self.__account_info = None - self.__ledger = None - - if isinstance(symbols, TradingPair): - symbols = [symbols] - - self.symbols: List[TradingPair] = symbols - - # init symbol statuses - for s in self.symbols: - self.__status[s] = SymbolStatus(s) - - def __position_wrapper_from_id(self, position_id) -> Tuple[Optional[PositionWrapper], Optional[SymbolStatus]]: - for s in self.__status.values(): - pw = s.active_position_wrapper_from_id(position_id) - - if pw: - return pw, s - return None, None - - async def __update_status__(self): - active_positions = await self.__bfx.get_active_position() - - for symbol in self.__status: - # updating tick - self.__status[symbol].__init_tick__(self.__ticker.current_tick) - - # updating last price - last_price = await self.__bfx.get_current_prices(symbol) - last_price = last_price[0] - - self.__status[symbol].set_tick_price(self.__ticker.current_tick, last_price) - - # updating positions - symbol_positions = [x for x in active_positions if x.symbol == str(symbol)] - for p in symbol_positions: - await self.__status[TradingPair.from_str(p.symbol)].add_position(p) - - # updating orders - active_orders = await self.__bfx.get_active_orders(symbol) - - for o in active_orders: - self.__status[symbol].add_order(o) - - # emitting new tick event - # TODO: handle _on_new_tick() from Strategy - await self.__status[symbol].add_event(Event(EventKind.NEW_TICK, self.__ticker.current_tick)) - - async def best_position_closing_price(self, position_id: int) -> Optional[float]: - pw, _ = self.__position_wrapper_from_id(position_id) - - if not pw: - return None - - is_long_pos = pw.position.amount < 0 - - pub_tick = await self.__bfx.get_public_ticker(pw.position.symbol) - - bid_price = pub_tick[0] - ask_price = pub_tick[2] - - if is_long_pos: - closing_price = bid_price * (1 - OFFER_PERC / 100) - else: - closing_price = ask_price * (1 + OFFER_PERC / 100) - - return closing_price - - def close_order(self, symbol: TradingPair, order_id: int): - print(f"I would have closed order {order_id} for {symbol}") - - async def close_position(self, position_id: int): - pw, ss = self.__position_wrapper_from_id(position_id) - - if not pw: - print("Could not find open position!") - return - - closing_price = await self.best_position_closing_price(pw.position.id) - - amount = pw.position.amount * -1 - - open_orders = await self.__bfx.get_active_orders(pw.position.symbol) - - if not open_orders: - await self.__bfx.submit_order(pw.position.symbol, closing_price, amount, Order.Type.LIMIT) - await ss.add_event(Event(EventKind.ORDER_SUBMITTED, ss.current_tick)) - - async def get_balances(self): - return await self.__bfx.get_current_balances(self.__quote) - - async def get_profit_loss(self, start: int, end: int): - return await self.__bfx.profit_loss(start, end, self.__ledger, self.__quote) - - def set_strategy(self, symbol, strategy: Strategy): - if symbol in self.__status: - self.__status[symbol].strategy = strategy - else: - self.__status[symbol] = SymbolStatus(symbol, strategy) - - async def start(self): - self.__account_info = await self.__bfx.get_account_information() - self.__ledger = await self.__bfx.ledger_history(0, time.time() * 1000) - - await self.__update_status__() - - def symbol_event_handler(self, symbol) -> Optional[EventHandler]: - if symbol not in self.__status: - return None - - return self.__status[symbol].eh - - def symbol_status(self, symbol: TradingPair) -> Optional[SymbolStatus]: - if symbol not in self.__status: - return None - - return self.__status[symbol] - - async def update(self): - await asyncio.sleep(self.__ticker.seconds) - self.__ticker.inc() - await self.__update_status__() - - async def __update_ledger(self): - self.__ledger = await self.__bfx.ledger_history(0, time.time() * 1000) diff --git a/bfxbot/bfxwrapper.py b/bfxbot/bfxwrapper.py deleted file mode 100644 index ba0fc31..0000000 --- a/bfxbot/bfxwrapper.py +++ /dev/null @@ -1,212 +0,0 @@ -from bfxapi.rest.bfx_rest import BfxRest -from retrying_async import retry - -from bfxbot.currency import TradingPair, Balance, WalletKind, OrderType, Direction, Currency, BalanceGroup, Symbol -from bfxbot.utils import average - - -class BfxWrapper(BfxRest): - # default timeframe (in milliseconds) when retrieving old prices - DEFAULT_TIME_DELTA = 5 * 60 * 1000 - - def __init__(self, api_key: str, api_secret: str): - super().__init__(API_KEY=api_key, API_SECRET=api_secret) - - ####################################### - # OVERRIDDEN METHODS TO IMPLEMENT RETRY - ####################################### - - @retry() - async def get_public_ticker(self, symbol): - if isinstance(symbol, TradingPair): - symbol = str(symbol) - - return await super().get_public_ticker(symbol) - - @retry() - async def get_active_position(self): - return await super().get_active_position() - - @retry() - async def get_active_orders(self, symbol): - if isinstance(symbol, TradingPair): - symbol = str(symbol) - - return await super().get_active_orders(symbol) - - @retry() - async def get_trades(self, symbol, start, end): - if isinstance(symbol, TradingPair): - symbol = str(symbol) - - return await super().get_trades(symbol, start, end) - - @retry() - async def post(self, endpoint: str, data=None, params=""): - if data is None: - data = {} - return await super().post(endpoint, data, params) - - ################################ - # NEW METHODS - ################################ - - async def account_movements_between(self, start: int, end: int, ledger, quote: Symbol) -> BalanceGroup: - movements = BalanceGroup(quote) - - # TODO: Parallelize this - for entry in filter(lambda x: start <= x[3] <= end, ledger): - description: str = entry[8] - currency = entry[1] - amount = entry[5] - time = entry[3] - - if not description.lower().startswith("deposit"): - continue - - trading_pair = f"t{currency}{quote}" - start_time = time - self.DEFAULT_TIME_DELTA - end_time = time + self.DEFAULT_TIME_DELTA - - if currency != str(quote): - trades = await self.get_public_trades(symbol=trading_pair, start=start_time, end=end_time) - currency_price = average(list(map(lambda x: x[3], trades))) - - c = Currency(currency, amount, currency_price) - else: - c = Currency(currency, amount) - - b = Balance(c, quote) - - movements.add_balance(b) - - return movements - - async def balance_at(self, time: int, ledger, quote: Symbol): - bg = BalanceGroup(quote) - - # TODO: Parallelize this - for entry in filter(lambda x: x[3] <= time, ledger): - currency = entry[1] - amount = entry[6] - - if currency in bg.currency_names(): - continue - - trading_pair = f"t{currency}{quote}" - start_time = time - self.DEFAULT_TIME_DELTA - end_time = time + self.DEFAULT_TIME_DELTA - - - if currency != str(quote): - trades = await self.get_public_trades(symbol=trading_pair, start=start_time, end=end_time) - currency_price = average(list(map(lambda x: x[3], trades))) - - c = Currency(currency, amount, currency_price) - else: - c = Currency(currency, amount) - - b = Balance(c, quote) - - bg.add_balance(b) - - return bg - - # Calculate the average execution price for Trading or rate for Margin funding. - async def calculate_execution_price(self, pair: str, amount: float): - api_path = "/calc/trade/avg" - - res = await self.post(api_path, { - 'symbol': pair, - 'amount': amount - }) - - return res[0] - - async def get_account_information(self): - api_path = "auth/r/info/user" - - return await self.post(api_path) - - async def get_current_balances(self, quote: Symbol) -> BalanceGroup: - bg: BalanceGroup = BalanceGroup(quote) - - wallets = await self.get_wallets() - - for w in wallets: - kind = WalletKind.from_str(w.type) - - if not kind: - continue - - execution_price = await self.calculate_execution_price(f"t{w.currency}{quote}", w.balance) - c = Currency(w.currency, w.balance, execution_price) - b = Balance(c, quote, kind) - - bg.add_balance(b) - - return bg - - async def get_current_prices(self, symbol: TradingPair) -> (float, float, float): - if isinstance(symbol, TradingPair): - symbol = str(symbol) - - tickers = await self.get_public_ticker(symbol) - - bid_price = tickers[0] - ask_price = tickers[2] - ticker_price = tickers[6] - - return bid_price, ask_price, ticker_price - - async def ledger_history(self, start, end): - def chunks(lst): - for i in range(len(lst) - 1): - yield lst[i:i + 2] - - def get_timeframes(start, end, increments=10): - start = int(start) - end = int(end) - - delta = int((end - start) / increments) - - return [x for x in range(start, end, delta)] - - api_path = "auth/r/ledgers/hist" - - history = [] - - # TODO: Parallelize this - for c in chunks(get_timeframes(start, end)): - history.extend(await self.post(api_path, {'start': c[0], 'end': c[1], 'limit': 2500})) - - history.sort(key=lambda ledger_entry: ledger_entry[3], reverse=True) - - return history - - async def maximum_order_amount(self, symbol: TradingPair, direction: Direction, - order_type: OrderType = OrderType.EXCHANGE, - rate: int = 1): - api_path = "auth/calc/order/avail" - - return await self.post(api_path, - {'symbol': str(symbol), 'type': order_type.value, "dir": direction.value, "rate": rate}) - - async def profit_loss(self, start: int, end: int, ledger, quote: Symbol): - if start > end: - raise ValueError - - start_bg = await self.balance_at(start, ledger, quote) - end_bg = await self.balance_at(end, ledger, quote) - movements_bg = await self.account_movements_between(start, end, ledger, quote) - - start_quote = start_bg.quote_equivalent() - end_quote = end_bg.quote_equivalent() - movements_quote = movements_bg.quote_equivalent() - - profit_loss = end_quote - (start_quote + movements_quote) - - profit_loss_percentage = profit_loss / (start_quote + movements_quote) * 100 - - - return profit_loss, profit_loss_percentage diff --git a/bfxbot/currency.py b/bfxbot/currency.py deleted file mode 100644 index 09a5839..0000000 --- a/bfxbot/currency.py +++ /dev/null @@ -1,161 +0,0 @@ -import re -from enum import Enum -from typing import Optional, List - - -class Symbol(Enum): - XMR = "XMR" - BTC = "BTC" - ETH = "ETH" - USD = "USD" - - def __repr__(self): - return self.__str__() - - def __str__(self): - return self.value - - def __eq__(self, other): - return self.value == other.value - - -class TradingPair(Enum): - XMR = "XMR" - BTC = "BTC" - ETH = "ETH" - - def __repr__(self): - return f"t{self.value}USD" - - def __str__(self): - return self.__repr__() - - def __eq__(self, other): - return self.value == other.value - - def __hash__(self): - return hash(self.__repr__()) - - @staticmethod - def from_str(string: str): - match = re.compile("t([a-zA-Z]+)USD").match(string) - - if not match: - raise ValueError - - currency = match.group(1).lower() - - if currency in ("xmr"): - return TradingPair.XMR - elif currency in ("btc"): - return TradingPair.BTC - elif currency in ("eth"): - return TradingPair.ETH - else: - return NotImplementedError - - -class Currency: - def __init__(self, name: str, amount: float, price: float = None): - self.__name: str = name - self.__amount: float = amount - self.__price: Optional[float] = price - - def __str__(self): - if self.__price: - return f"{self.__name} {self.__amount} @ {self.__price}" - else: - return f"{self.__name} {self.__amount}" - - def __repr__(self): - return self.__str__() - - def amount(self) -> float: - return self.__amount - - def name(self) -> str: - return self.__name - - def price(self) -> Optional[float]: - return self.__price - - -class WalletKind(Enum): - EXCHANGE = "exchange", - MARGIN = "margin" - - @staticmethod - def from_str(string: str): - string = string.lower() - - if "margin" in string: - return WalletKind.MARGIN - if "exchange" in string: - return WalletKind.EXCHANGE - - return None - - -class Balance: - def __init__(self, currency: Currency, quote: Symbol, wallet: Optional[WalletKind] = None): - self.__currency: Currency = currency - self.__quote: Symbol = quote - self.__quote_equivalent: float = 0.0 - self.__wallet: Optional[WalletKind] = wallet - - if currency.name() == str(quote): - self.__quote_equivalent = currency.amount() - else: - self.__quote_equivalent = currency.amount() * currency.price() - - def currency(self) -> Currency: - return self.__currency - - def quote(self) -> Symbol: - return self.__quote - - def quote_equivalent(self) -> float: - return self.__quote_equivalent - - def wallet(self) -> Optional[WalletKind]: - return self.__wallet - - -class BalanceGroup: - def __init__(self, quote: Symbol, balances: Optional[List[Balance]] = None): - if balances is None: - balances = [] - - self.__quote: Symbol = quote - self.__balances: Optional[List[Balance]] = balances - self.__quote_equivalent: float = 0.0 - - def __iter__(self): - return self.__balances.__iter__() - - def add_balance(self, balance: Balance): - self.__balances.append(balance) - - self.__quote_equivalent += balance.quote_equivalent() - - def balances(self) -> Optional[List[Balance]]: - return self.__balances - - def currency_names(self) -> List[str]: - return list(map(lambda x: x.currency().name(), self.balances())) - - def quote(self) -> Symbol: - return self.__quote - - def quote_equivalent(self) -> float: - return self.__quote_equivalent - - -class Direction(Enum): - UP = 1, - DOWN = -1 - - -class OrderType(Enum): - EXCHANGE = "EXCHANGE", - MARGIN = "MARGIN" diff --git a/bfxbot/models.py b/bfxbot/models.py deleted file mode 100644 index 607b207..0000000 --- a/bfxbot/models.py +++ /dev/null @@ -1,295 +0,0 @@ -import inspect -import time -from enum import Enum -from typing import List, Dict, Tuple, Optional - -from bfxapi import Order, Position - -from bfxbot.currency import TradingPair - -OFFER_PERC = 0.008 -TAKER_FEE = 0.2 -MAKER_FEE = 0.1 - - -def __add_to_dict_list__(dictionary: Dict[int, List], k, v) -> Dict[int, List]: - if k not in dictionary: - dictionary[k] = [v] - else: - dictionary[k].append(v) - - return dictionary - - -class EventKind(Enum): - NEW_MINIMUM = 1, - NEW_MAXIMUM = 2, - REACHED_LOSS = 3, - REACHED_BREAK_EVEN = 4, - REACHED_MIN_PROFIT = 5, - REACHED_GOOD_PROFIT = 6, - REACHED_MAX_LOSS = 7, - CLOSE_POSITION = 8, - TRAILING_STOP_SET = 9, - TRAILING_STOP_MOVED = 10, - ORDER_SUBMITTED = 11, - NEW_TICK = 12 - - -class EventMetadata: - def __init__(self, position_id: int = None, order_id: int = None): - self.position_id: int = position_id - self.order_id: int = order_id - - -class PositionState(Enum): - CRITICAL = -1, - LOSS = 0, - BREAK_EVEN = 1, - MINIMUM_PROFIT = 2, - PROFIT = 3, - UNDEFINED = 4 - - def color(self) -> str: - if self == self.LOSS or self == self.CRITICAL: - return "red" - elif self == self.BREAK_EVEN: - return "yellow" - else: - return "green" - - def __str__(self): - return f"{self.name}" - - def __repr__(self): - return self.__str__() - - -class Ticker: - def __init__(self, sec) -> None: - self.seconds: int = sec - self.start_time = time.time() - self.current_tick: int = 1 - - def inc(self): - self.current_tick += 1 - - -class Event: - def __init__(self, kind: EventKind, tick: int, metadata: EventMetadata = None) -> None: - self.kind: EventKind = kind - self.tick: int = tick - self.metadata: EventMetadata = metadata - - def __repr__(self) -> str: - return f"{self.kind.name} @ Tick {self.tick}" - - def has_metadata(self) -> bool: - return self.metadata is not None - - -class PositionWrapper: - def __init__(self, position: Position, state: PositionState = PositionState.UNDEFINED, - net_profit_loss: float = None, - net_profit_loss_percentage: float = None): - self.position: Position = position - self.__net_profit_loss: float = net_profit_loss - self.__net_profit_loss_percentage: float = net_profit_loss_percentage - self.__state: PositionState = state - - def net_profit_loss(self) -> float: - return self.__net_profit_loss - - def net_profit_loss_percentage(self) -> float: - return self.__net_profit_loss_percentage - - def set_state(self, state: PositionState): - self.__state = state - - def state(self) -> PositionState: - return self.__state - - -class SymbolStatus: - def __init__(self, symbol: TradingPair, strategy=None): - self.symbol = symbol - self.eh = EventHandler() - self.prices: Dict[int, float] = {} - self.events: List[Event] = [] - self.orders: Dict[int, List[Order]] = {} - self.positions: Dict[int, List[PositionWrapper]] = {} - self.current_tick: int = 1 - self.strategy: Strategy = strategy - - def __init_tick__(self, tick: int): - self.current_tick = tick - self.prices[self.current_tick] = None - self.orders[self.current_tick] = [] - self.positions[self.current_tick] = [] - - async def add_event(self, event: Event): - self.events.append(event) - await self.eh.call_event(self, event) - - def add_order(self, order: Order): - if self.strategy: - self.strategy.order_on_new_tick(order, self) - self.orders = __add_to_dict_list__(self.orders, self.current_tick, order) - - # Applies strategy and adds position to list - async def add_position(self, position: Position): - events = [] - - # if a strategy is defined then the strategy takes care of creating a PW for us - if not self.strategy: - pw = PositionWrapper(position) - else: - pw, events = await self.__apply_strategy_to_position__(position) - self.positions = __add_to_dict_list__(self.positions, self.current_tick, pw) - - # triggering state callbacks - await self.__trigger_position_state_callbacks__(pw) - - # triggering events callbacks - for e in events: - if not isinstance(e, Event): - raise ValueError - await self.add_event(e) - - def all_prices(self) -> List[float]: - return list(map(lambda x: self.prices[x], range(1, self.current_tick + 1))) - - def all_ticks(self) -> List[int]: - return [x for x in range(1, self.current_tick + 1)] - - def current_positions(self) -> List[PositionWrapper]: - return self.positions[self.current_tick] - - def current_price(self): - return self.prices[self.current_tick] - - def previous_pw(self, pid: int) -> Optional[PositionWrapper]: - if self.current_tick == 1: - return None - - if not self.positions[self.current_tick - 1]: - return None - - return next(filter(lambda x: x.position.id == pid, self.positions[self.current_tick - 1])) - - def active_position_wrapper_from_id(self, position_id: int) -> Optional[PositionWrapper]: - if self.current_tick in self.positions: - for pw in self.positions[self.current_tick]: - if pw.position.id == position_id: - return pw - return None - - def set_tick_price(self, tick, price): - self.prices[tick] = price - - async def __apply_strategy_to_position__(self, position: Position) -> Tuple[PositionWrapper, List[Event]]: - pw, events = self.strategy.position_on_new_tick(position, self) - - if not isinstance(pw, PositionWrapper): - raise ValueError - - if not isinstance(events, list): - raise ValueError - - return pw, events - - async def __trigger_position_state_callbacks__(self, pw: PositionWrapper): - await self.eh.call_position_state(self, pw) - - -class Strategy: - """ - Defines new position state and events after tick. - """ - - def position_on_new_tick(self, position: Position, ss: SymbolStatus) -> Tuple[PositionWrapper, List[Event]]: - pass - - """ - Defines new order state and events after tick. - """ - - def order_on_new_tick(self, order: Order, ss: SymbolStatus): - pass - - -class EventHandler: - def __init__(self): - self.event_handlers = {} - self.state_handlers = {} - self.any_events = [] - self.any_state = [] - - async def call_event(self, status: SymbolStatus, event: Event): - value = event.kind.value - - # print("CALLING EVENT: {}".format(event)) - if value in self.event_handlers: - for h in self.event_handlers[value]: - if inspect.iscoroutinefunction(h): - await h(event, status) - else: - h(event, status) - - for h in self.any_events: - if inspect.iscoroutinefunction(h): - await h(event, status) - else: - h(event, status) - - async def call_position_state(self, status: SymbolStatus, pw: PositionWrapper): - state = pw.state() - - if state in self.state_handlers: - for h in self.state_handlers[state]: - if inspect.iscoroutinefunction(h): - await h(pw, status) - else: - h(pw, status) - - for h in self.any_state: - if inspect.iscoroutinefunction(h): - await h(pw, status) - else: - h(pw, status) - - def on_event(self, kind: EventKind): - value = kind.value - - def registerhandler(handler): - if value in self.event_handlers: - self.event_handlers[value].append(handler) - else: - self.event_handlers[value] = [handler] - return handler - - return registerhandler - - def on_position_state(self, state: PositionState): - def registerhandler(handler): - if state in self.state_handlers: - self.state_handlers[state].append(handler) - else: - self.state_handlers[state] = [handler] - return handler - - return registerhandler - - def on_any_event(self): - def registerhandle(handler): - self.any_events.append(handler) - return handler - - return registerhandle - - def on_any_position_state(self): - def registerhandle(handler): - self.any_state.append(handler) - return handler - - return registerhandle diff --git a/bfxbot/utils.py b/bfxbot/utils.py deleted file mode 100644 index 6fa5c7e..0000000 --- a/bfxbot/utils.py +++ /dev/null @@ -1,59 +0,0 @@ -import re - -from bfxbot.bfxwrapper import Balance -from bfxbot.models import PositionWrapper - - -class CurrencyPair: - def __init__(self, base: str, quote: str): - self.base: str = base - self.quote: str = quote - - @staticmethod - def from_str(string: str): - symbol_regex = re.compile("t(?P[a-zA-Z]{3})(?P[a-zA-Z]{3})") - - match = symbol_regex.match(string) - - if not match: - return None - - return CurrencyPair(match.group("base"), match.group("quote")) - - -def average(a): - return sum(a) / len(a) - - -def balance_to_json(balance: Balance): - return { - 'currency': balance.currency().name(), - 'amount': balance.currency().amount(), - 'kind': balance.wallet().value, - 'quote': balance.quote().value, - 'quote_equivalent': balance.quote_equivalent() - } - - -def net_pl_percentage(perc: float, reference_fee_perc: float): - return perc - reference_fee_perc - - -def pw_to_posprop(pw: PositionWrapper): - pair = CurrencyPair.from_str(pw.position.symbol) - - if not pair: - raise ValueError - - return { - "id": pw.position.id, - "amount": pw.position.amount, - "base_price": pw.position.base_price, - "state": str(pw.state()), - "pair": { - "base": pair.base, - "quote": pair.quote - }, - "profit_loss": pw.net_profit_loss(), - "profit_loss_percentage": pw.net_profit_loss_percentage() - } diff --git a/main.py b/main.py deleted file mode 100755 index a0935d9..0000000 --- a/main.py +++ /dev/null @@ -1,140 +0,0 @@ -# #!/usr/bin/env python - -import asyncio -import os -import threading -from time import sleep -from typing import List - -import dotenv -from flask import Flask, render_template -from flask_socketio import SocketIO - -from bfxbot import BfxBot -from bfxbot.bfxwrapper import Balance -from bfxbot.currency import TradingPair, Symbol -from bfxbot.models import PositionWrapper, SymbolStatus, Event, EventKind -from bfxbot.utils import pw_to_posprop, balance_to_json -from strategy import TrailingStopStrategy - - -async def bot_loop(): - await bot.start() - - while True: - await bot.update() - - -loop = asyncio.new_event_loop() - -dotenv.load_dotenv() - -API_KEY = os.getenv("API_KEY") -API_SECRET = os.getenv("API_SECRET") - -app = Flask(__name__) -socketio = SocketIO(app, async_mode="threading") -bot = BfxBot(api_key=API_KEY, api_secret=API_SECRET, - symbols=[TradingPair.BTC], quote=Symbol.USD, tick_duration=20) -strategy = TrailingStopStrategy() -bot.set_strategy(TradingPair.BTC, strategy) -btc_eh = bot.symbol_event_handler(TradingPair.BTC) - -# initializing and starting bot on other thread -threading.Thread(target=lambda: asyncio.run(bot_loop())).start() - - -################################### -# Flask callbacks -################################### - -@app.route('/') -def entry(): - return render_template('index.html') - - -################################### -# Socker.IO callbacks -################################### - -@socketio.on("close_position") -def on_close_position(message: dict): - position_id = message['position_id'] - - loop.run_until_complete(bot.close_position(position_id)) - - -@socketio.on('connect') -def on_connect(): - # sleeping on exception to avoid race condition - ticks, prices, positions, balances = [], [], [], [] - - while not ticks or not prices: - try: - ticks = bot.symbol_status(TradingPair.BTC).all_ticks() - prices = bot.symbol_status(TradingPair.BTC).all_prices() - positions = bot.symbol_status(TradingPair.BTC).current_positions() - balances = loop.run_until_complete(bot.get_balances()) - except KeyError: - sleep(1) - - socketio.emit("first_connect", - { - "ticks": ticks, - "prices": prices, - "positions": list(map(pw_to_posprop, positions)), - "balances": list(map(balance_to_json, balances)) - }) - - -@socketio.on('get_profit_loss') -def on_get_profit_loss(message): - start = message['start'] - end = message['end'] - - profit_loss = loop.run_until_complete(bot.get_profit_loss(start, end)) - - socketio.emit("put_profit_loss", { - "pl": profit_loss[0], - "pl_perc": profit_loss[1] - }) - -################################### -# Bot callbacks -################################### - -@btc_eh.on_event(EventKind.CLOSE_POSITION) -async def on_close_position(event: Event, _): - print("CLOSING!") - await bot.close_position(event.metadata.position_id) - - -@btc_eh.on_any_position_state() -async def on_any_state(pw: PositionWrapper, ss: SymbolStatus): - await strategy.update_stop_percentage(pw, ss) - - -@btc_eh.on_event(EventKind.NEW_TICK) -async def on_new_tick(event: Event, status: SymbolStatus): - tick = event.tick - price = status.prices[event.tick] - - balances: List[Balance] = await bot.get_balances() - positions: List[PositionWrapper] = status.positions[event.tick] if event.tick in status.positions else [] - - socketio.emit("new_tick", {"tick": tick, - "price": price, - "positions": list(map(pw_to_posprop, positions)), - "balances": list(map(balance_to_json, balances))}) - - -@btc_eh.on_any_event() -def on_any_event(event: Event, _): - socketio.emit("new_event", { - "tick": event.tick, - "kind": event.kind.name - }) - - -if __name__ == '__main__': - socketio.run(app) diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index b00bccc..0000000 --- a/requirements.txt +++ /dev/null @@ -1,36 +0,0 @@ -aiohttp==3.7.3 -astroid==2.4.2 -async-timeout==3.0.1 -asyncio==3.4.3 -attrs==20.3.0 -bidict==0.21.2 -bitfinex-api-py==1.1.8 -chardet==3.0.4 -click==7.1.2 -eventemitter==0.2.0 -Flask==1.1.2 -Flask-SocketIO==5.0.1 -idna==2.10 -isort==5.6.4 -itsdangerous==1.1.0 -Jinja2==2.11.2 -lazy-object-proxy==1.4.3 -MarkupSafe==1.1.1 -mccabe==0.6.1 -mpmath==1.1.0 -multidict==5.1.0 -pyee==8.1.0 -pylint==2.6.0 -python-dotenv==0.15.0 -python-engineio==4.0.0 -python-socketio==5.0.3 -retrying-async==1.2.0 -six==1.15.0 -sympy==1.7.1 -toml==0.10.2 -typing-extensions==3.7.4.3 -websockets==8.1 -Werkzeug==1.0.1 -wrapt==1.12.1 -yarl==1.6.3 - diff --git a/sounds/1up.mp3 b/sounds/1up.mp3 deleted file mode 100644 index 14a165d93af7a884a5e917399eddb8a63a8c953b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56106 zcmZsD1(Xy?vu-C*RR>QWdwX~*|%5YQ6u^eA2MW6!+wMN^c*s%_~2e65$f4(WVc7Jj5efH z5QDdr7zwGuXBBa2+%eul68NUTXXBHAGQO8l-mGtwGGr0|X^H>ag8ALJ#{a!*miM({ z^lQA$tG>TR59V9`r(gN6B<58^O99`Fm(iagm)W-2Bl(~949U!zMjfNff4_{j4Y`cA zjFyeEhAh6`&Cf=C-*vM!UmK>h|63Yg3A1Nk3%-(OP2WAES5qR>a>fW4S4@e2_G@To z+%csv?wc(bdKni(=!1q~K&VShbR(63>wf{i0YL(s9V%)LKvJN_sJG<{VcC_iwxnYJgnSZK-4ZK-zfRneQ`f8GSPB z>0@-oGShlRy(n9eCiE^_nt3j5t-FF{spYbzOyIIC)52@|bleN&!?R3L4`lXEZuKK9x!1S-A4ep;;+cMP{5B{{=>=u87LfGi z(}Kiw?<3kfxO;eXY>fiRQTJ^h+?&!jWYkGJ>Qt4c;m*9*a@CLYWR;Hm{6pN~4_cff zeWRDiXuE&dtI%o6F=tBRfrJuY>%Kkx?vKP4BAcz4eT1XG;ts#sBlAOF~!YKJ7GF5zsDc zlPnu(cX!v68S&@8*8i~e)6UepVxE1dR{=jg=4neXLT$B(S9V+O_! zj(ibVTTNsgy|tb1Ji`v|qGyB)9!@@QR=)wW25A zkCES=e;)T?)wlZT^|0zc%N}bfptR?eTx)*ZP1v30boY@-*5jd`9DzAcL~IJ`s!r$0 z?hnq7`Z{aZtebLN%W28=Hv7AvyBMXt`QAEibfVpPTAs0o2K)KHP+I6}+Ny+}pWnXA z{Y50Dd$TLTs`w*Ey1GvvniiJ4EV*)ed;NoYH~2?n%jl=sb7hHkR3vUsHg_Yfv$8wD zk?ptWe$gmAA<#;)xtFB`{-~GIBy$n}L`U22+Vfcoie{O0eth~??(4O0)sx-svuK^A ztL?OPwUUc9$ef+pDK#du6Dwr>CnPPRZ&X59^`NbmrJT7Rx_;B0%J2X!+kx!Mvqxl$ z^SdEKGXF`s^rLy|V^>$PfEM(d=V!HMii)l|DVO78zki8~OJ3+Hjuq=~ewFR>X$z5@ z8IZOjrE11#O;f4_y$IVISs}c1@IYH()YyC9mE!3ygKbxXQTWb?&)Mb#E>SmY)zW7r zilr<~ZDe2d zyfxKYi~cGLdWxktPP&!YGHGynKJ5y+PFvc_SqrPbi#P5n&KKzuGe_(7lnwqhv&_i$ zJ@l;q7|V6pLMz}o=G`cnWqCl0&?i|QgxqvoR&MI{%!=v5Gse0z*?0Vm_O{lr)FN&@ zpQ}h(k>obX71CO`14NYax23GL80~?p=+|7Eobk@8-m0>`^`F4?q02*~1Bctr5-g(xlTO9R`56b=~@@W`@TKJxT&#%J_!ebM&FgGy~nM@x)aN-p-^acZgeQVOIs z%$%>ELub^(mZP+yGDx)YPIHaOJn5d#K9hcai-VemSc1a*Fx@EI=-a$O`b&9_20LyA zdV`7u*0>!k>B?yGwXpXiY>f{7BiUK39t1 zU-?q+MAsRo+nM5ep;={a(pnv_=2y0(vn*6Q>&fYL>W`(SuC{-2>~I{m<)Gv6JN`+( zq!$G=hWtC z514{t)d$u;?G5Z-EkenL%Zfd0E88r-VJlr@eQ8^1n_&r4M#>OIHRf%pALcgXM@lQX zl~1^#oXPTN9X;RNCp>8&>v}Yev{TNK{y0O7VP*8|T6VTV+{82JYwIjqMe6~zBAF`} z@h|KcuZhBx1GJ0vj`fD6r0S1T_&|M?R$k9w*(66p$S2Z-EJI=9ldgHOx1@K5CfO3% z51%1_lh?R9dcuEa)AcXb2yc>Etw8Ehg~sJVZ{$Z&$kE3Fr$RL^Lc>UfG|IVO3Z;PGw)5 zUA;@|SZdIL%2K>e<`X-4TTu-SByH6#G>PV>`IJ+rmFUOr>CM?5-d^5APF$PZ!@W>1 zv6DsXU9_3ndVMoLD$C$%q!gKkbE3lHGpor~vM5myJtH~kua-F4N*zU(qQAvm{)|_W zJ@7~+l76LIX<79X9w#gC6y2$(vAH4-8igy6X!0xWE=Thh`Ub7L=GNA*t6~m%j@y$j zI05C5z4%O4n>FEKate-B@6d9VLbSCq8PAhB#eUvfv_fM^XLSR8NByWx*@|k3p6rF* zm7U`~E~g)5R0WHb&$xy5@{pY3ABL}e6D^3sBq zhqS!fl}tj1#U1{MH<2Up6wu%`T}E@LH*rt-D^Jy}4DofMIGO+&gp&ODcR7GR0}Y01 z0s2AqMl3<^a2xUpzeoPEE1$)FXI*&_xfB-z4f0vC&`Qc6JW)oABYc$TgJzJP>T3Fk zW~gao9x5xkv5)!?_K=T|Uy&PEA}4VT)L8rp8VrOnzM{Y2cVsBWuV` zvKpc}a+AU|yX6u{+Klu?d&DiC$~($Ac$$)n-la1rSB~LkG9S;-v$0@)K$J(*acL4j zLUAqGg+J7{Ym>AX{Vq!u8_@^cocx0yA|X2PxvV`K#;eM0xSV>6hFXN0N9hQj7cTzh zv&2ZWlJrrR)64XY`izW0c|}+DU7yZA@#!)ZWrB^i<5H-qSjuwgW3-dndp()ImCfLK zPSPHyOB;+qD|VH&6Wx%%QiO(B_EVx(C9Tj_ahrSjD7gr)P$K9JI*@)-*5N8LJNN1_ ztPnpd>Y|yrEJ4JMi^|UYw!TlBt(DZjGF9$C3E;oC@I{m?+VMH8FZ+YHl3+{qGPPMg zsXo3NjNaYu3e<@8>@{$H<6?l11^T$&C_t zI~K|=vclrD48;ORkulf_WhbzL`W8*qE3!r+7>&Rsi4&K``{Z{RgMloNZxAO?9T_4z8c16GzpGMo%jchDoWySkUep`6k# z-tz?U8a*cy)q%7RZK75pOg;b&3bGHZxp*LBF~dQmGkzciAIysBXS96!KsG`YK~rFc zf5JKN0{IHYU@|MpuZXv(2Waqw=AjdmYdAxW5ifWx@m|K^GfFU>PTgt)B@2EmlEFg5 z*%F>x&OkeHG`WlSpf{o*zpOvgzG(;bF?_(k>v%4*0QW;1L`}Yq9b*~%sw@cpzL9RA zP1U93H420=_{w#Wh(3@>YERmlmQnMOM0pjw=vU@s-JtJ6;6*BFfiKAnK7bX6Q7)~| zWHUuYG!16>YaEED$@}~xo6f58ry?B<0u8Ry0NM-I{cmz0Sg4Umlj-=15=ckTuj;Rg zK$k=U%fqIzzj!gZ6z#>~uw$-44@7=`R{yA_XgBl)FlUos6^tZPa3{1_RO4INd1e=n zWK}XkT@U_VN}Wosz+Pt)soWtMiX+q2wzM9NR)fe>c?LA7!W2GSB+B0~oJ?>Hd`y1h z16fi1g;q;n$JUElXgA=1-cnen6pl(6-Y+mrf9mT z%{R0AEJA#jjmda*DV+e44kE{q6gJ`EIiv%pfCf!zIjX20d`<4<-B}%$jZYOCoPNAG z6PLqVUoOf%9rXx{tn9D=G`{b=g~d z<^x3t@+S`zLOavrsztenc8NEvB-_BQ^SZEa?ZcsDH=c$Lh#dTco}mTn$@*EoMq=<^ z8=OpQp;4k1-^pIHq99L4GEJRD`@xRco@_@>&;W^IGCOv|^@cPbO;SGM19AuN3@b%W zzFJ^Z6}xdVE{s>mXS^FLt*2^zV9&oOI>9;~LoVa*sFgg+zp}qz&-a)4@JgisT?e+# z3Fod8va5*aV?+Uzl{`@pZBDnqDt;FIDW0vLz{#j zXrSsv^cePypOiru5jSp!V^I&72M1xlX)I#Vcrr^JPpi>qN+~iGeG^uZRWz1mu^luh zLsfcFIfNI<#k@c3#~Sj(A_uAi8ob4!c#OQkTd`U?)feh+7B7aOO)&cV@dH#|9^>)s z2<-XAWfQzr$wQ~ob!rCbfLF_oB8{&Qbx=|AR`IAM>3H=RS&k-%`>Zj$!;ly$&!JOb z>qWQ`%vT$DQJ`K~FTn)A0-mAa4BP;RAvpi@lZ^5naFSg_=BUGIVR}i)L59KlWfM6> zM_ChRRi>)NVE%1aHiN8lK!b6tGruhIqb4{7zlQlbK%V0*SzSF;|4R?ynPMXP3ufSU zd=(Xy2YCuR!*=t^vMcOdx#%!DRsBTj;>EI!aPqC9H7ZNqD;a8j+DlzarlEo29&5+m zFo&2aFQE%yqZzm|nk_JBkX^5<*Jr`viHw02hRjv_(Qvv` z2_c=)LlGeIf*l&dljI~dAN`;%ROaBmaxxzdYfE4LM3g`+a4No!Gf_u*0-nSg={fb| zdMpo+)6s62XX{`m$|3jgG&q@_JSPao{?>Er?etEpp!gsQ!Wg{A72q_`Kvd?JSsp$e zY_XdxP`gl}u2DR=8M-ALqNJDzmdgcW5JB&%)D5~n@GJ`*C=hiO?Jk_+6_%i`OLPAtG@pKw?3xk zW)Eha|J(XceGGoYkklyW)572rett8te1nTHS~RU-+%>eNNc;DHgF*VK$$!i5V<}9& z!YF0RWt8#V`^hI55}4YWOofk0Fnc#_@3V-nJySE2b6iq3O{)}sej=rm=Pno*;Pq)V`9zv{oCEC)! zQXUQPRB?66{OW9)Ia?mId{hI_5pssK*9tp>JpaIR{9WtZpx7)2Z7p=KcA8;#Nj)Se zFl<-wUc0a!7eQ&_$LGv(AbSZV*ygY=hav%+!AIJL8tSqpYd6&dMrvCR(4dEY6iYo@dy)glvx5p8Hs~!nj1{QKvoQxNEvC zJ;%FTgCb(HJhHva==rJscakw#t41D$?8;VJEtB3PA=mf8Zv$S>|FMERviESz_b*_* zlv(MUGwyeHUh>?UJ>quWp)phZY_d8Np5EGf>(a>eG2f%KZ0r2%xchuTU!qfDyO;e~%i`Pd6MotK%Q-I7Tdt_@^wX-OKKgO{ldyhydga-eZL)Pb z@8jN=QC)Nl=^48z&!L#OY+hw+>Mx%k#8q&{i7mDop>aWN(8d(+mr)oQ5$4zu5E02SA22G?6@B1H*KhJu7{N1e|eyowDn7`ew>q}i%is&nvvGMo zmH2q&`x9q&+{pICzRt3RAJ5pG)F@@DdyBd*%ZVH>V^)Tx`5hAtU4gD1Vu|B@czDjT zF%z@ar>d(=;`#W%)PvrRYDY&ye=q&VTRA1++t*Lw-zKFV!X+aI}g^VkSvwm#Kh~ZmlcR%7+ zSefGKlicY0qObWA)XXpxV^bV;Y)NRPD=pb#(j;qlpLhwXEW7SGe$BwbDkaPN@EEGc$>+c-4bwLht79Q*z#X^T*AkW9fG5b9w{q^kz8yoNwK0)PqzUEgfoh z#a=?_dvz-w%~rXs&e)97nZ2}Y@^7UYwW{0DQGJT%qPwHENw%;!{7(b~JE~gh5=-I9xmDzz!OI5m~WBNk(E9Wt1BXLAa=Ox+B0p-8M}PL;ctvdOW{QrdfCSb#c73-BC)4rC#Y??)||wqLxY{ z6)P=dus+qjFf$`Fz`KvHz+NQ)Se|*}i&oj2=&ic!T2|Mn*6LR`aLl;%odyq~w% z;@!2~TRl10O*x4yQmz6wlAnLqdTCK?o2;P5*xK0VTAQlL=p$doj_|Dbo;uyS!`8`~ zs+^apx}SDPX`Bef{+0B>7;xNyUB z;10l9v&oZejc#N4#VCwuto4iKl$w={m+SdtU__dbWOck{ieN6EhJ5C0to+Xr98F*QeqP}cy_y| zCemqiys{eI;7lig{?evZleW&#y`-0*GlQV`6Ia&w<0eANsATRfj#TMPsr&4#j-e`_;mlm*WR(p{gr~&@^0s|QZRy>Aw{>u)oM}+_{e@~<71M)-G@mM2 zdjji~2+!ETLS_53@!B5UD(ay(z!xnCob){KT|?O$-VXIw4pWC^liHmWMs>si{z6p2 zhm@}LF`!<#NI7|jz0}vT2BI9=1LLv<-;lHTdVQw06Bzn!;xq~)#Yhd@Mz#T*Z7b_5 zLU4qdL7&oK^#$G{k$3{=_IF%RT}StVW&7cUq7O^ZJ?y5~jJ^UlQxBH~o~61zL5tIF zuyQgluqih1#fN~1v|%2mizoP)n$t2Btk@JdnI~YyG&z;50e)ow9j6>ZX*`yNu-?3e zRB$kfB2UnMv5;xnXsw3cjb8>;b6rJ=$JUhdx}LU z6p+5#G!(3;%MP6Djo34OO11;t5P-}6VKM*AVk!wgV(T!>WQb&Gd-}LpYA>i;^fC<}%AHpm>2^`PgT1mD} z96=URfYb)&bs!(g4zjKSW2w9ZHZVwikM~G_@til8k8zkf1F-b0>TtY4Ol0Z04v6MT z;9-lC2EZ{z@wUKwdBKV$WF(;dge0KnA_{O_!7{{I{HH4DC^}hb4m{#>IKzLE0|7tX zMtjiF$`!QdNP&AS&fX6-r9-|S?OTOVAz%G_zPqoe330?A1=n62@ zD{!X##J93Pz={=6d1WN*vNP2oq&(^_E&_8HgJ%I-x{t1csJM>O%`|-*t0(fHRlo)8 z0JQudzXNRIP30^TDg_&W;I1L@DT;TieKdg{$9VNJ=01<9LQ>vigSQ3PJ{@MMZoN}79Y?hWe?o} zXN!;cs*Dw%crCdP$1BZ&QwUTi;mhDBet@z5EvBIxz+pDSon;L^7ud59{R)c(ZZ-$R zYNVnJQJzm@7XC%7!tK?2v^`j?Bd#NV0QY)d)+8;|akK^eL_6RLdh%c8XJGdH zz>i;u+pM~NK09G6haUdO;UB3*m2AMn-*0vI42@XO;Tx2VtTy8{R_TB|7 zcNl4e#sWWl2_Dc}E1EioPFG98E*lNwa*S0IVGtpa2e?O@N%qLs|J3zOPgO#Mr9N$4_C6&jHpsF2~o$GWsgMvR3*Ht)Jc=_|e~CR$j;RfqA8TG5F#bS&Cd!uh9h6 zMc$w<;w|qa+N1MiwK|4=Q9qF4XfH1ZyT*OsX?_8w^b;P7dWumjyZ%tSt*7&>u(sub z865^l!eiJA9`R6=KnekWIaVDCr=mG9E*F9OtER-MBOnH>G+8JM!?+x0)!}q828RKk zB4ADhf_GWNW^L1pr1q5F%D~0IxAY z=>vXCM5Opz<{;5(6B-LAosX!Q7!R>0eRy7ZAL2y_foKomWP^eE9HX~_Rj@XU)pfiG ztrS`LO5mw`Lj>AhSOY(*9+>@U5R2SdG(vmG3>cT!>I+!eF7bxIFg)T*Wgg%!Ug7Cz zIIKe@^aSm(p2Ypp3Sg*~;24w>Mr$8?z%3AWWT(67AayjL{>#K0ep-A%1(YXhS2_-2 zNLI?~yfiz-Dgp1+AN#`|O;IK<#Qb!tUYAVER5d~U9eqUOpCyNJP`K*{y$P2kal8M%h3orN=b)ZV=-$6*2x8&#|!Mj9*9D) zad>{vCg^SXJXrx}C0Fojh=$3-cYx1!k^yACx`{r58J&PMkS#Gc5j6#t;4Nv0 z?(+_82D{4_!wJ-lpW%6En)nM?Cb#witVqxTn173KK~!9%vZG*auRKlM>RjM929QB; z0*!~6_84VVj;RgkV6`&Y4zsN$@F10i8_rGyd@%^Aq6&-B!}WS>9I#f`U=B5a7>1kt z4tvGwh)t-oQkV9n=}Ib2l1+pQW?El7ADEKT)TOM)RHpvRsP2O3ku|_+x5mqWABzVb ztR7gA%I7E@Ct!gJ1K)8H7@j+#7WODZfX^HWu~VaDgoqM5qz7jLFJ^~5+JlCIhP~N9 z9xYFyJ2(wk$=E&=m#5TXl?utB_~bm7skV%-8OrXid3fG?Ir zQ(>j8K(D}zZjIjae!vyo=W}ELPQnlH5{QL4&U)y6dYt~9YjP@h&LaFPsw@j$?9|van8E~UDV5O`M{C7*52v+pS4nlyBw1a28*J=TXFWP~#NWz=42)<3U zMN0tgZG+d!?cAeB=&ittMBaetm5-R2}~V7fTW27?u|1BQ7GMnJ(~vJ5|<2kU?8%^?mTKg`ODcq5!?YQTIM!W+xa zxPv;BUR2XbI*J4kxx2^E2pb@XgjqYxd&58zC+2Q)SmqTUSLMU$|66jiCxc!1$>x|c7&yFf-QYJ& ze$cpX%7oyWiNyPu0Fz_zq5VclQy;@7hV+KkMqT5&(TWf6H!b*68}q(V-+Y<%jZ%gT zMlFMFG5R*NGPnnG6n`OY%u++SicEXvy@Uf5it| zm~4w_d-I!F-_Y66)#ObKOZn=W+M0bCEt(pdlAEpmU!ODWZ`#A?-_Y1(V|+}MVJ}k# zQ*T38QyL?u%h1?2GKLg})}}QK`OJF0E9T5FZEW^rc&-Y}tI2{H?fm40jC)2)#weI< zprMhelX2a+YxZn#m!?F%xo7&b&!)yzQwOte!+NIFrnVIB8!egrn35THell!6Z#8Xg z$mEmrUm^aL!*|7Z&ur6Z*XZ9Ip`ZP8V0bfRAe>PWc>(bVqTb)&^>~6DCEAIiXaH%B z(#1h`LT-VGzrxBA$Td4EACShDI?7R5OZ%NC!(_=p3hPOl)w@uBfe6C)c!Z~?XsC{{ zU$jO-%ywR0+3%vggxa0g$vnj!=r7tIXLrxYeB{gp=vN(-Ec<%i%X(_jfW3Y@cy4}( zUA5hEJhL{}!=1B4aT0BN!K=BOIGbc#S0dE9MDB#$*#47ESmQr*5Gfp zO_`pQ_gZec)<2KdDt%I7)wD8dRZDS8Gv|l&AUWIbafHpDk5ANwQI;(@w2w70rGN5a zy}Z4CP)}`9^6sxCQnOq8`%$G;V#mbcC~MXuF*^d*3oUc1`ZC8aF((3hC0+WyO?&TP zL4`AWe{sC8mA=`pvULIPmC!nAzuX6ny1ykIGR5f}97P-d8a;oy%` z-*LO3N!GN~uHV+W^oTNr2SxT45vif5XY`r^m&2-LuKVImaj7vui%GwPwXd&yPGWw6 zJN(LJmi+!EeP(FIf_b8ckV|Ph^^;-!^QA@((O-S*ozxY74=84NlQQq!+K+oZRsD1O zyFI1jJEVSejL(}d=DMY|GuYz_o*UaUdOThnKQQqVZ|0xXe$;vXPB3MX&QeCD_vy*}Fj zrMRoLUM47CRKKie#g?Ss)4Vdk?uTFLh&}Vf5cozRu<*nqx8tKC#g)gEKm(40P!@i#3dVb=S@O zRXp{-9%8Xu#1ijbJSbp#@H=aow}`W*7Nh)bv7>pJNhvt97g`JXkGH+2GmEpEc1y@o zKc~Fl?ImL!Jm{glJ^Ra<#d{c6vmC+m-D#=WocqN_z<)mLgIxW!ixy8%g5v_Np>5>X zY7XjTs-F>FKpZOOKSgm$vC-vXe0sf`@o#dLQo_h^X zP#UQ7`2EcJ8N0j;W$+#A@ml$XUM_|JUklt)_b@`#{#J<>Uaj zma)bYjnjZdyQ(esSVVQ(e1Ds@s_dW-KPWvaG6v!%Pg*s3&CuJFR%5qeIw zs^gn&H4bLUd_P^~SJAdlVsBCHmi(eLAc5?PE7=v!b#h-x634u^w00zi-|zOln9b-J^qiDn?F`&+d?eMP$CoJiu%L4kow7kS}3cBQb|+1l2PwAZ*MV?tR#Cx8OZM* zOtx9uT6*KT@T@;UDQx{jo1*@Z?>tLRgP8df{GE59cQ?<#yGcL!T_3=r@N`RmOL0;f z;*D#=&TX~y#g*7r{i_Hd`N=ME60DVt|BnA4xzK6AP+hVSZE1N&-iSTCJnlqOX&n;A zFX`RHY+yF}%YAwYZ8uws>H(fT7oK9nWl^=frLQs=u=-wTnfi$`k_0(WpScZq8GqD{ zwbg|FU6v)?u|*VwDA^uL3rnbKK|A?A=~B|^-(dG|dJ{euRU-$Hl~2=p>Lq0^h@f65 zeu0R~F(jYmFZC4q!ilV)JfnTozhxvmJ$qz+;8afXBiaXTrg#fa^*Q8VR+&%5SLkn$ zM_67A5F^Pp%GCp?D{va!rG`)9?!uxk*Iw}95M_QxzF-g86jUBqml|ZX(D+tdj}E6R z@q3mHqKJ=RJ0WtS-dBs|A5azGJFD9cv*H&yj7}LRw{qV!#oG7t-hpXl-A-qWc%GiRY^N^hz$oGbDOM4 z=HPsy42u;=DPk$3wnPtjcexof=1Mu4sYmj95GCtI?f5dtY$DPMIeF*BK(?Gy@`*lF zL$IItTYMl#=vFlsD#muQb25sI!%qH8d!+p?wgO%=N*)5lO2N|roxhJ;i?Sk;jG=|7 zikGpbteAX9oYSGMrT9PvO zAHXeEib+bi<)-42w;?Xst6ZgXl`|qmtlc_L~-`lBp-HhUtEtFPz|!oWfjJpt_JH?n}+SqIS=5VjpM8;tREHUzaJZ_!jf zi22JTh0}J*R=~+hp$3qF6Qa} zA^+ti;I=jKYtFQAy}$SqX3a#fP$d5qFN1ujdAO?R0U5tNX`*@+?E(b0A!O=o$4f*P z{k*n=mjjkiK`$WdqZ6{ywX_tuC_=>`90;h&QCylAVMoO@Ku+q)C;A-C!~Q`{Vb(MN z%y*7#px&nmq#5KI{Dl?)-nN`P2kyGChz36c1c!~$Ikr9@;bo+XAdFW&&sCoR+o=rJ!RJ1LuKfZ9S9 zVEKUSDMDPp5PgLFjHlu<{))59F@RNH#%CcKJOX^;nAk}s(Pe4{G!_`K%`yY`huq=P zfaHdWiNJVmkVk-X_>Ag8&f9f71LCM(<5E^&4T3tzy#yOe27Qr!*f9%S}cm- z*B~2tEAY9qh*hRQEW{nPBMl)#VAf0(9U!-6juiT0?IFyXqT~qL0UT)_`AK<7t15Hl zCE!XMsEg=V5-M!W#l2vaXf&H`)XK6X83aC2LJVW=MXZuR8`>n9)LEqJvT&J#NFYwgOA@4heoR)RiVQnL;jB*kMf8*oWL$O@>LWje%=M^4=DCB8|DZ@k* z;M*5aUeXMGWY;v0z7{Mr8MlMn(_-Ql`AAi@8H(e5WiMqiy`t2Hy!l$Z8f2|}M)P=* zHVjTCp8$<~DHcJtb39%_7pn*t5dVmMqzCPy7Da;rV>|%}b2pq{8d#q7XWdh`tT?CxGt@22eU@Q1^1W|>k6hr`B z)cXMIFdfenvzedhO7_rS)a}S7a?4<)J4Naqn2S-|4w*^0@lCGko%LJdJU)*L%3F}1 zHw?D|RJ&?f|>(6dr=Mmj$|pZNzUWYLbWW7B>Q=NHb_*0 zHh0L@?4EXnwMD-|uHON$P=GwD9HYgQ9iZ{A0OPcwA4yN~8~XseYY=ISKCru5F7`l1 zlPb83Si=U0C$JjcRvG{oJWdV-jW;SqWJ%VKw?pg zH5XvT+{K!MPprV(#1`L@E<)}+ZK{!*S(8q<=>F;R@2=j*^Hro#U9MYF+NOu(J+3ULhbf%}lT5QQ=g zGH=VuoJv#JYevXctUfP*0^$6zmFLmd>+xbU_{1;r5}VDJ;hvC@6ore6b7C~f59oXq zv=exX&GId7gPV#;dJVm#XaQ%1b26UA@|(!3s`Ln6EV_xm@fY=+njPnaoZ9SiIo^-m z5QpW_W^xVYbQx3zvbVglCuFd7A|WzTc+pg->%rT3Gl&SQf}Oy0G+=ME6gC^8w0EL& zz#aT1w=3hQLs(YiCYOvCwbLve(S67R`UdQi!a zzVo$mv{I7}RnkN=_9q{LLdjOto=56QdR+3@oa_#5sduK|lv8yBVf)F!yESSRujO&y|cLSNWr)?Xfm%%&}3 zKg7JH^XhQkd@jRyEFXb(s1B;*hoZ9Rh7YNaA%dVeHLdvgnnbJs( z2M)3#a^Xj4Kd-Bw(y=^^&td`G*kyhZH-z0d6;&4>#SG#{W7WLqB-7Yw*gG17)hFpK z^)6x@{sI|PE{J0Iir%P~)U^<^GX&!N{!tgH6505ER!VMwGtFo5K+gfQCJ1|RFw9X0 zu;z`_g0wEdGMfkk{w_hiji|&dQs!XMW;Y_OY>vDpUmlje!i^1#! zp8~(dun=|Ozv^7?4kza6cqd?_QKB4a0gUD{;8KgqHzbH&R*p-ETjd;f;6R+oyXY(R z2XG2Mfs4yz*lRk#uaXQ?tAS7K7QaGdMiX^9ddNPr+43?TgQtm&dJ~v6)gU|YmaNFD z!TSHF`VC@q&WhS%GTx}}QybvQ5EW#FUk5phLu4@fO<%%qL#F*8G@mE2V={|cjgBR` zAu{SAs-mW<7V;0D%BG2ez+&x&C)6BzJgWsh^bPjMGjJvytaLGcJ40F_tMf82fhWl{ zF`N-`2BqNIaHc5;dEVE6Ev*SBg7d)k?j)@sgT5O4vdJ`ALJ6XMmFnOVGkJaZy^VWl zBd?~v*2}@HnT9`!?<|ww!;v(<`VL~ueuza7JEkIVFugj-MrcJcLolkk@2FjQb`dV)7U!Q((&IliyduY|ZS;sAn=7 zhGd3*CdcIC6wF?XJLX-Zw69h3s%cH*zWK@cYrM>MjH|wOO+AdeKF$6u`0pRHzWLo( z-q(W32^slLKC2j`;_JoNs@cEK21ZN1HVs{T<81cuQzo+|W7JH(#$aYl?#tv;%vMb8 zjUG(i&S=Guz*pAP(&)pmtxqCTezR?JJbdL$DU3UYuX+E?$n%x(No#yJJYg7X!cKn}_ON7YwANpZAYS9Mp< z&aCgUu(-QJ2*KTgYjAghyL+%8fdmZ@+&w^WcXxMLVckZ$Oa42Ve^1_zb6A<}?&_*8 z>*vZ-!95f@uwzD{fIPuX5K~Fu9Xx3P+d{kg-Li$6%Th*oTB*`nge@QQ^Sn-5n>IGN z6Q6CgAqVls`P$jfb5TpRRSB6Ho<~o!(!`&hAmh8x%>ktXH_Kazvz^^!h@Rv>-e~Wd znN~3MdBR^-Y5O>Bo}PzKOx%%iSF02FIor#umX_#DcJ_4l^e>b1eRz(bkMe8mouq@F zL{cMkilc{fQ&RVs17BWd9I#!r7jP_*6XND3k5Frkx7my3xo;0sx#2gpD=;wU4>@Lq zbybU^o5hTFE!3`uPYS>5xfJ)qm&$MZr%d)M5!fam(#jfj@bgP&&490Yv=W1}#cAp8 zaVcSboeQojJ}duaha;iu`w~ejNkH)atf6v7)Y`WvpC61nWZw~(*H*+jAA2>bW=1M= zh66_>?47PhI-fI`*6|CHI&x_e3m%lx&A=FM?I-HLU6shHe> z_6cS|v&D&TKflZJu5-eCtyj>Qz*N;Y;g6Wmv@>>+KcVp7+3y-PGjgRzYkeb!6rP*U z70@QVLe$-FpFP|Bte`2jGwBPyR(V$>D#FvzUM0AL?V*#!6^s9er)4=QE00Dx1eFurymkQZr#|Oibztx;EgIe_QE^fA+D+r-4asx;r3$z%J#N{2=aj+An^I zxtkZ*m-Q;E>D-z=UK<^;ARo(JHlU94Wvn|P-b}U)4;)~mWb}-A{INvrEKgg%!GYtA zD(?9Sj}r%qU&2=A`!Sa@ppo!TuVi)#=$~U>o?Th;kw25>#jQ@?Li6~qbF7lhlg@m- z`*lXjWt!9fjbpCLliD@0QpQ<~SEROkeMA?z%v4 zhuE@@$hzA;)LlBMZmNTWx#>vPXS&ZOsF*#8FU^-meMdD`)ciH2Od8l^K_jy7k7((L z76+Xdt;6=IS>9xG1s5Rw(#j@xb=9SZ{igffl?BtZ_yckN8I9?2zjJ;q)bflzsheB_ zjl5YVW!o8?rfu>xaId9NL5{2+!&msV^Zc0dI_($!k~Oi<)(3i?Cq0cjm^{|1VG#Q# z{gPQf?NhoGp8_Uj^$%}o&nEA>dh$hndBOk)29_m>>A6$qyKVHeZKLhEyq~@}v1H;} zr%4{z&eFY`DP7IPntilT6mw(-)tq#=4lf$JiHFe?dPZe zWX7lLNj#I9jW1;{Y$NnxmX|r{GSU5>&$7J;?h;ljpakjWbf?Yp zyeF@~BaV|DomZ1*CckpsCCa#O^i&PqBQh>|%CNq{HN!0b`dW3%&#X_^`X_}93f^r* zn;+AUJNt;UdRf~U&1G&*yO-QJeJcm0-pHrFvido9y4uTHj;f)%g8JG9$k(0*Vuz76 zs9eYkM+q|BH7TQ~`8O$Rgc?Ns*73HeR*#?04=k*XzFoMug0Dxam9V zyjg%&cRUN47Z}SHTHT%Tu8X2R-j4fg+pQWI$*ET}ii%Ns3FDI1j$dCMFIJjuP<2e@nuA5} zjQ@u~fBPvqlFzpoz3+EBV7q@_R)^nm75C(mm*H-dO{>X2Iiu5SyLO0o@Ga}9UE|q2 zr#+YCH`|MVV*Ur&SwtlhL}_;1Q97WNUjee+)5!h6nnS+nhp<2YTGJI|P-NibWg zA5p@T?vi}D*3VHlz_Kl(?|1}HB2R1`{ku9==_mL#cZR30?5{cXmvk4`+|`{e+%7Ry zON27IsIWX^%~Tm=>)@Ya4~H@^5B#q7>r?F)97AmpWV%_`(}9mA!}W4{5A~N>&DG1* z(;7B0IFwa{{V4)Ri34f`=in5{dlE;jSWKU-SaD?nXyoeS5%ludn4aJ{z&RRD1YW>x5`c zazK}}SN+LXo6s3R$CMAe?pUz$n?W1ao3=J0ZE^Z*QciwB?DoF?!YFU}X@%umsBj`x zYw&Yy)FEeBbls|`~-#e3OM3xJy9y)M)-9%r2v z9l*nnriauTz5^PJaM6S0)SM_eJ6w7miGj4S5rmSTqvUuoiQLj3!Ewf+6_szGzssW9 z)ApJ`oMM*M2(h@^axm?t8FZ=~0e8a%YKUIcR?BFOH~C#qNz~VLl>7`OALa4zbnFc7 z`Zu~?t>Xt#a-`^uC?JJ$t{}Lgx5Y?W%g}6z`c3fu{ zIq3wTIp6q5L?c3MVd&d^)d9g}IV}}P>rt5JU*aC?t!R$cj;9;ce*V}zVKxLd8m_tF zht^7TM&F)_*a|o9>*q)-u&*bP%laC`>C+I`58}spcC>aSErS$8ofn|ak5K2*T3$K^ zI*nBRvl^#UTQHvUK($}kpj1d_N8xp-lk=SL_o5jzOY!htI>{fJ7tLm(A<3#G&`YWd zlz8!Y&XT3VJ?T7YhdNIp7xdq;zQAQm2BY`pf>&gD^swS`j`fFm(RwLI179kP7|=YP zfN0=EJ;SgY?X)iNN|O?^1UqQ#)*0#?#J{4B1PZa0Kqw!Z56w2B4kDiK=^fP#n5$C^ z1LEQ|j_B9_t@AAM4}8r1jTl5_!%=b}^%E_LQB_bbvsRf`toK0eN}}Z6aurVjqBBu{ z3q@K}t+ToUx3auiG`v*zqRv;a27DGR5hMG8lJD^+<}2|2>XN+L3n+9tK^1C)u0cWb zx>vtVTB6R2$UYs|sqq-d59)k{=SLL31<~N2x=KCf_svAJiD-pQSv z=8?_%93ufG*Fed4cnQ@At?MDVOOqwED{PkW zr|wp-d3n^iJ)*hS;f!_<`r2Ttq3Dj8XfSoAEo8(sg25Bm7(6O!3ry4I& zat)x4FL`-Y6}r7xjH*l44l^AHK|9nrgUmu}e+9nKNIz#}umajBc(u7@aqTRdVXW5E z)kj{A$6yXiq&J}AyoE@0pw(3LB5t4`AE4D;2e-1WGz}We&H7o)B%yFNUI_*B9rlve zl!f^x@D{2;FZ!93mRGDjrm$jgrvIKG5c)kv-k;_I5%d3sSb5^p5b_U*@EA=E5 z!^?t8&=D%Mt5B@mfsQi-YNg&J0WsI7m`S!EX4#L%!r5@M{tsylFSiNgH+`USoxMiM z#W7+F<2e`8K9EZCDzHsI{vLh1G_;T_<$BO+~ z13YEBkdLVIE_I)0MakVoPZERJ^$qyf{sla{AAQSKqi-LAKU;(xM^->@dkTVsL`mihB@gRaPQ^tB_Ax|k4hE-t@;~E^BS@~PeaL#p#XmY zZ|95fiwxyou^S2mHoQ>o<^{xDRZHJzoMgq(w;#eMIt%nRy^WvIx6|S0=YTKcOM08G z1_D(Cnv)4wL6YfX_{C0xlV|`+K7x5?uD>2BG9MXxsy#CV4||VfIXfTWUUQ zHs-Jl+{pzs6TFCQvL7(NB~aGwBSTQ;5vKQISQXRDD!{7bJnzIsG45DxiOSe)ed#` zr<35#S4>RCc%Ffy^J!gGtcX+^6p%HHzPhP0;M^A^n~~4p9jsOgb*_avH^R*F552AC zqRwSyd%6enPG9{PT00yH#?kQU-NH@*7wW(Tud3?8*)N(@ksmB+W(C603~iv(@$xma zOLJ5meIztR;aD-CidA-P5peXb*oS%eKs@JG(0U&ODo8{bt10T-3~0n2`btd{+j#}q zlpchyZEuX{elojUM270+jCJf7Z6n)5yIccvn5KQj?l8gP;3)Y)xd=rmnJT})&2}k9 zRd3YUA1j6v%p)Bo7e#Bkl;9&!a%+^l54#~EDgnEhjkPNUCBIW+fn$9KcGw}PnR@6a zQF30?c_6$fSF%IEQo3_LsD;a-b>E?6)3RAbfNR$Rb1051l%J5(WtA$c_b_%a0tQ|N z{NQa+LFG4k>50k#_tZd?{2DBs9g6d6R(ER(X45E?{8^0<=TLGz^zAlAceHj-SsL?p zPiW=lpyUoHIS3_}LXUcbk_}F*?|=hU13%?6SqjW1H?+V-^)ALXWlMq+s`bA(Nd2C zLOPyD!0{b37)s{6HA{m7*VZX2pusl*Qo(*jP@WRI6BIN|am>nhG zL~HNDbM6AikVUBT1C;z&O@Q-sA*|2qfywvKw_rS1k>la<;xGoF&h7F3QWPbZhvM%x zb`4=X$m+s9V2DxeIUG`7!3BJ$VtRLD3hF!=s*bufCbso!0iCQRG(W|7b48?lW zle=)IZu*MslrMNAu~`{t?U5`U-u&^R2>Ny>77j0;B)mCIgL_H`XcBi|PvQ_`t>M<6 zc&j~&JGly9g8uMO^`kZPh44A=PI9XtRh29Of9Eh=PsS*RYOS8A%A^hnAyF#f->A6{ z5%DQ{GUNNcc)B-Y?u*8IfAU4_eLwl4>fTR$2!;tJJKuY4Uwiwm;yu%MPhM=(i)eW7)O%H5dt}zcSATD9GFvh8I^OTz<9$bXvCd3{ zB=b%)ujYLk|6wPYS~&0ZGIdt}w;y=_l3ALsWqnulefD4b`0m;JH{a9CRPy<1=snK& z|G(>bpNki-^0k!riF<42{XMfh?>WAD`JSNf3f{WVtGBQp22)=!1xwqMZy@+?fxhqrY*oNDr$(_A7o zZ+ac=S1^&*X~o?;J$5$MRweXT|3};sn$?#L2>R?FXg`usDeb=8OG-GpvDO*XStw;# zMq_xv_t1i@qACBFKiE2je$DD)3GO`Z8D@OQ(X8&ka_n?c-sH#TM}2bedQvBCVdBWB z*o+@+VSWevo;Y3cg?SCuCp;s^3L6#G&0Sh-L{ir4Vd=6(O!uS=tDGZGmK5?XaZl9x z&yC#QJE{dd(<9=4j>_+TA=O|J$cip ze6R1koh5Sy-!}|7Jti>bn0YtwRMDD6O9U=+holX$A1yqi%*29))Y6X!KRiyK9A*p8 zWe$lx_GIIWX3jAGHKCQ+!1(cBr>7MTxmcurkv0*#tk$WgZH4nqC|)kl3H5hW?AL+r zN&aPnSIES#bzii7*DB+0`ycbpHNMndA+ygl|lA$^(Sr{GWe`Q%@t^QQe7Jio}P0$+macp9eMqeFA} z7deotn6~lT_~=mA96vp{iC!Sd^}f;jZz)5NAM2W}X8O076)B?}K?SlG+!z{f-AL&r zk41b}P~~oHE0UNJ{Utrb$QP7sR7?B#iGCQLR9wFwc-)@d_2gUo&FWFtqM;{mUn;QFFsTUqbz)IT?=mpUrZ&pa41 zD$n|Cet!MkIh^6f?W`MeH4AHmz+l^?iolqd|132&`Fd2#*bvWCTZ&_aHah)QVs`iV zfFn7lMSkySZboO6(ng18$(a&9m()ovlGM$-Xq<6;rgPG6#EgqIT-(_b#{jm;nVR&M zvy0!;$b`s;{sVb==dY?{NV)78S^DZvQ|BaIbbrztJ09v)oWEETw3N@qP`3v85aK*V!nWd=#ESmxN~b-cDGE3C6eX>1P zcQS6J9pdW)Mnp^w>Cf_-Gd;E0;*i2wRtNMK`P1&CKM@m+6ShX`RC>y|HD}7uuR>E8Lw^MDk{LOYNww zjQ+xm%Q)t4p_dG)6B_4tU;gD8E2HdtLtX|A)rYv>rB^YdXn9*zeSn!Stx3u_S7~au zz0&vcgHC^UQ%Zv#grwN3k?dv(k!-6G{3Iy1QO|6aF~Rek%mZprhF?v8oU+oHp~_*W zTvPtyy5!oTcKQ2qo0_pN3-$d zny01P#Y@=yf=>GP*I)8Y)^S?j(K)bzy}WATp5lHh41ERbMB00t>D1YTAA%=E8Cuf1 z|g5F~7wDm+TwJB+t+8K+zbvJId3YW)^2mMoY^{`y=bs5bp5Q zw9adD{7?C3H#~B+RbTb79rF)xxadZ6jJpqCKr6yO%CZW(4Cit)AC>wn?Kd&X^VIA` zHOCbHPsVfg${H@2an@1BQBuEXopZmi?trb=U#lXDxjQ>;W<@ex*Yuf^na9l>WQ1RN z|M9kMq&pln&gh5i*&T&hG_T0uJ1V^!n8X86Iu zj5b4FrdOgMJ%rdyQ>N>$$_9kcHX?T0x$$;Kom%7~5^j_1%cERaAZSa<)Up4Vqg_ zvQ~;Uq?TiC4&s1Toovji(31qLcd`NQ|ca3yy0q~wT{IE*ETVHJ` zc!rqEt=#GZ-k0tn7t|2`3{0Py9kcuJm{O2 z%ZHP!#iDF?HV+G&&)r~vEn*(Mr~<%*^o~V z#c5My^pV;uI8KdG74`i_4x<+oVc)DzVmGM@zeqb2RWr?(yalPLtp~q(DsrauB#&8M z+hpC3+!JwfjW!lpyd2sEcrgwZ^?*!n1==;w8ihE(V^s{1_Oj{>{F;uaAAyLbq>SEqXEAC{__Vo~&{py~(Z^ z9du;YK`xV_aJLK9HmYHKhS?H|Ao#|B@!UW(fv?*K%|ecgvp~Pzf~DRCInGk`v#J^I zj=0-6#9K>}Xui_4Th-vST3x$IRw7?Skj$+eg`Td47Ar4_isXfU-I&UTkR7}zv_5%o z+ z1GxexKyBF7wEW(qkXN?E7OQeq!Qbag~*XCimZM-pzFHiGoWP?uF!U$yaupRttw zDpITq;ulf^ZfdpB7k@MNKz$UUt)SshRV4H8$#nJ+Xi01G5P6|~(kjBwu(6f}y1lM& z$0L|!9>{0#=qwMG`v>UEkh=tWvNh_y?lcOpYa~bxfy3l$U_Hs&Rv7{f$zgSyzN7tB zdA`KFV--`ofmbD{Kp+osvWV`1x~3lO1&6ELbRydiNmKy#Xz@r!W;_4 z)_6FCwjtBRJl-Dqi$_qVU7(NTdoi8Z**^H@#i{kk+jmPg11jPY-8DywdvJs;2BdT_9|H|b zq_Nb9(hjJ<#Sp9?!;M$0H|>QU94~f|{8}mCt)s0?W-{*r1Zx>s;Ik2@A4+~@H{jlt z4fuDqtRnK^D2CP!nj#Ep<|!0!BHB-&IM#2AZnVJdyvSQuHL`Fj`82UHa%EV??TmhLPa3<8K{2JlaYxs3+;v|U=6wek@k9e zHq{DlgT2%i9Cuj#!f%_4tbgFQ2tQo(#RYtdJgPl}lhp>?NsKr^3b3`t4gH#0$j8D3 zst-jA5Qj)ND_B2BFCuY(d`2vkNk6CvBaQM}1oUb7$WDEcQHMSj2>MZMSqpstPAE}Pz>6%6JV9z zhlcUBD!|?t`}CjD7x!T{jRRWHU+W-?LZ=Xqk$;9V=m@Tw1^5W{mA*!P&>Z42uc;R4 zpP&dz2l`eYNXc5DSel-Owcrb1sE#4G&1?ncqq)nSK&t ztRcC8Y&yqeVVYB$3KdiU)F5#p7K$wg4F-1pT#TRvj5g48w1iUbIuO5Z#sU@#f9g8C z0dmmg0MFp7Xk(?DUByg{@3wTB+=-R!Z*mo`q;Bmha_-eohv9hpl`Wy0ML9SMjwC@) z;CGd~tqthXYDyaXgO9TD*oS|wQm`Gi6c^WQ*UV01q) zoA5nw^*x1ba)D5wjDtq=v=ObpAZ4I?T8H>_Gvk`}P}bmKP{LoJcai^eB7X$5xI8>B zV~~@ykywu?t%1?ln{}k4WkDH8>%bwgk3L8ZhU;h(H5Izb81)_;ruo)B%%+iOo!{WT zJwg7cjWZswoAf4B?Gwo(M3obOsh{Et`BPa5{eBo}D8j8)Rw%}INhpZZz_=1(Ijw1w zF=}Zw)MpW+I_MptfZR@UiY{>5{YoMcr#=q<+#Em{_LAaQp+?BF(0|2{kF39ON;`_P zx~s?Ni-)0g`dw59e!2{(c{y599z_1KB;FF7n0_=v-a{n373t3wBeq>0&TyY)TlfxI zh(2cnuDVqG0L95T=qK&`tvQvygFa-R7X{#P{Dis%7vXb2%W}#^a3|lvN*EioO|lxV zAs(nJ^bq|~^#n^H5-O3uXe_YnqT(7j2>EeVZI%zuvzaVQTQg+7)+?%^z=sE`SsN#?S-Uo4$olV4QlT&o$QT zqu@gN541H0nH??$!{m>=zevXZXdBd?jW9Ob^NDH~m>&nB$8n**)z*K5Pw-dZ!o7g8 zw}m>>sR^J9p+F*6LWi>piqrsT<-W;T(4B>0HhqG5V7oROt_^+YQREUkPuzMVc!sWr z^Y|s68<-+Pe#tOV#roaqhc;!POBzPutgS+fy%!_Zaer$kS z=EIe|kd*~4|HyR+zsEV^6@Mk)XhV(C z(2M;g%Om$p04t8!e~Q|{-(h|p21Zg8^tRQkgVuVeNTcW|=-`il2U%UK0!8z6WU;p3 z&#@I=@~8FO>#*C8Pxx;SX?7+zTXg3H`)AvxZtj^x)5`3_MSZ zLd9B5Oy%R{Q!q2GkaV#Sxb8Nw3TIWv&ix`kDqfLafP9^V>c9h);BGK^7T`T*4-m>= zAaJp?6dec@yS!Cc6vbKNX{_|{-capk15Te`F9`Nq3fLKk*&P;3%V3AG7Y@VE=p^LU zEQU;}8~GW`1Ak(-R6#^y$Glb_WGsO{d~>;6uBU^T8~LWs!26&n=KuYe2b!wi;i&l? zZ-bn>=g~T�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 diff --git a/sounds/coin.mp3 b/sounds/coin.mp3 deleted file mode 100644 index db62eca1194958d09b3ffb20b8b25044a1019acc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48444 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 diff --git a/sounds/gameover.mp3 b/sounds/gameover.mp3 deleted file mode 100644 index 2a0905f08a731e202e056c43b4b304b605a443dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 126456 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� 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 PRgw*()xZ7yKY9CK^GNay diff --git a/static/.gitignore b/static/.gitignore deleted file mode 100644 index a5baada..0000000 --- a/static/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!.gitignore - diff --git a/strategy.py b/strategy.py deleted file mode 100644 index 920dd85..0000000 --- a/strategy.py +++ /dev/null @@ -1,123 +0,0 @@ -from typing import List, Dict - -import sympy.abc -from bfxapi import Position -from sympy import Point, solve - -from bfxbot.models import Strategy, PositionState, SymbolStatus, Event, EventKind, EventMetadata, PositionWrapper, \ - TAKER_FEE -from bfxbot.utils import net_pl_percentage - - -class SquaredTrailingStop: - def __init__(self, p_min: Point, p_max: Point): - a = sympy.abc.a - b = sympy.abc.b - c = sympy.abc.c - - self.p_min = p_min - self.p_max = p_max - - e1 = 2 * a * (p_max.x + b) - e2 = a * (p_min.x + b) ** 2 + c - p_min.y - e3 = a * (p_max.x + b) ** 2 + c - p_max.y - - s = solve([e1, e2, e3])[0] - - self.a, self.b, self.c = s[a], s[b], s[c] - - def y(self, x): - def inter_y(x): - return self.a * (x + self.b) ** 2 + self.c - - if x < self.p_min.x: - return self.p_min.y - elif x > self.p_max.x: - return self.p_max.y - else: - return inter_y(x) - - def profit(self, x): - if x < self.p_min.x: - return 0 - return x - self.y(x) - - -class TrailingStopStrategy(Strategy): - BREAK_EVEN_PERC = TAKER_FEE - MIN_PROFIT_PERC = BREAK_EVEN_PERC + 0.3 - GOOD_PROFIT_PERC = MIN_PROFIT_PERC * 2.5 - MAX_LOSS_PERC = -4.0 - - TRAILING_STOP = SquaredTrailingStop(Point(MIN_PROFIT_PERC, MIN_PROFIT_PERC / 3 * 2), Point(GOOD_PROFIT_PERC, 0.1)) - - def __init__(self): - # position_id : stop percentage - self.stop_percentage: Dict[int, float] = {} - - def position_on_new_tick(self, current_position: Position, ss: SymbolStatus) -> (PositionState, List[Event]): - events = [] - - pl_perc = net_pl_percentage(current_position.profit_loss_percentage, TAKER_FEE) - prev = ss.previous_pw(current_position.id) - event_metadata = EventMetadata(position_id=current_position.id) - - if pl_perc > self.GOOD_PROFIT_PERC: - state = PositionState.PROFIT - elif self.MIN_PROFIT_PERC <= pl_perc < self.GOOD_PROFIT_PERC: - state = PositionState.MINIMUM_PROFIT - elif 0.0 <= pl_perc < self.MIN_PROFIT_PERC: - state = PositionState.BREAK_EVEN - elif self.MAX_LOSS_PERC < pl_perc < 0.0: - state = PositionState.LOSS - else: - events.append(Event(EventKind.CLOSE_POSITION, ss.current_tick, event_metadata)) - state = PositionState.CRITICAL - - pw = PositionWrapper(current_position, state=state, net_profit_loss=current_position.profit_loss, - net_profit_loss_percentage=pl_perc) - - if not prev or prev.state() == state: - return pw, events - - if state == PositionState.PROFIT: - events.append(Event(EventKind.REACHED_GOOD_PROFIT, ss.current_tick, event_metadata)) - elif state == PositionState.MINIMUM_PROFIT: - events.append(Event(EventKind.REACHED_MIN_PROFIT, ss.current_tick, event_metadata)) - elif state == PositionState.BREAK_EVEN: - events.append(Event(EventKind.REACHED_BREAK_EVEN, ss.current_tick, event_metadata)) - elif state == PositionState.LOSS: - events.append(Event(EventKind.REACHED_LOSS, ss.current_tick, event_metadata)) - else: - events.append(Event(EventKind.REACHED_MAX_LOSS, ss.current_tick, event_metadata)) - events.append(Event(EventKind.CLOSE_POSITION, ss.current_tick, event_metadata)) - - return pw, events - - async def update_stop_percentage(self, pw: PositionWrapper, ss: SymbolStatus): - current_pl_perc = pw.net_profit_loss_percentage() - pid = pw.position.id - event_metadata = EventMetadata(position_id=pw.position.id) - - # if trailing stop not set for this position and state is not profit (we should not set it) - if pid not in self.stop_percentage and pw.state() not in [PositionState.MINIMUM_PROFIT, - PositionState.PROFIT]: - return - - # set stop percentage for first time only if in profit - if pid not in self.stop_percentage: - await ss.add_event(Event(EventKind.TRAILING_STOP_SET, ss.current_tick, event_metadata)) - - self.stop_percentage[pid] = current_pl_perc - self.TRAILING_STOP.y(current_pl_perc) - return - - # moving trailing stop - if current_pl_perc - self.TRAILING_STOP.y(current_pl_perc) > self.stop_percentage[pid]: - await ss.add_event(Event(EventKind.TRAILING_STOP_MOVED, ss.current_tick, event_metadata)) - self.stop_percentage[pid] = current_pl_perc - self.TRAILING_STOP.y(current_pl_perc) - - # close position if current P/L below stop percentage - if current_pl_perc < self.stop_percentage[pid]: - await ss.add_event(Event(EventKind.CLOSE_POSITION, ss.current_tick, event_metadata)) - - return diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index 7940a79..0000000 --- a/templates/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - -Rustico - - -
    - - - - \ No newline at end of file diff --git a/websrc/.eslintcache b/websrc/.eslintcache index f238dc2..d1449c5 100644 --- a/websrc/.eslintcache +++ b/websrc/.eslintcache @@ -1 +1 @@ -[{"/home/giulio/dev/gkaching/websrc/src/components/App.tsx":"1","/home/giulio/dev/gkaching/websrc/src/components/Cards.tsx":"2","/home/giulio/dev/gkaching/websrc/src/components/Overlays.tsx":"3","/home/giulio/dev/gkaching/websrc/src/index.tsx":"4"},{"size":4418,"mtime":1609331626715,"results":"5","hashOfConfig":"6"},{"size":5688,"mtime":1609331022076,"results":"7","hashOfConfig":"6"},{"size":5235,"mtime":1609331232067,"results":"8","hashOfConfig":"6"},{"size":321,"mtime":1609332875579,"results":"9","hashOfConfig":"6"},{"filePath":"10","messages":"11","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"12"},"1ev2e5",{"filePath":"13","messages":"14","errorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"15"},{"filePath":"16","messages":"17","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"18"},{"filePath":"19","messages":"20","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/home/giulio/dev/gkaching/websrc/src/components/App.tsx",["21"],"import React, { Component } from \"react\";\nimport {\n Balance,\n CurrencyPair,\n EventName,\n EventProp,\n FirstConnectMessage,\n NewEventMessage,\n NewTickMessage,\n PositionProp\n} from \"../types\";\nimport { socket } from \"../index\";\nimport { symbolToPair } from \"../utils\";\nimport { Helmet } from \"react-helmet\";\nimport { Navbar, Sidebar } from \"./Navbars\";\nimport { Statusbar } from \"./Statusbar\";\nimport { PositionsTable } from \"./Tables\";\nimport RPlot from \"./RPlot\";\n\ntype AppState = {\n current_price: number,\n current_tick: number,\n last_update: Date,\n positions: Array,\n events: Array,\n active_pair: CurrencyPair,\n available_pairs: Array,\n balances: Array\n}\n\nclass App extends Component<{}, AppState> {\n event_id = 0;\n\n state = {\n current_price: 0,\n current_tick: 0,\n last_update: new Date(),\n positions: [],\n events: [],\n balances: [],\n active_pair: symbolToPair(\"tBTCUSD\"),\n available_pairs: []\n }\n\n constructor(props: {}) {\n super(props)\n }\n\n componentDidMount() {\n socket.on(EventName.FirstConnect, (data: FirstConnectMessage) => {\n this.setState({\n current_price: data.prices[data.prices.length - 1],\n current_tick: data.ticks[data.ticks.length - 1],\n last_update: new Date(),\n positions: data.positions,\n balances: data.balances\n })\n })\n\n socket.on(EventName.NewTick, (data: NewTickMessage) => {\n this.setState({\n current_price: data.price,\n current_tick: data.tick,\n last_update: new Date(),\n positions: data.positions,\n balances: data.balances\n })\n })\n\n socket.on(EventName.NewEvent, (data: NewEventMessage) => {\n // ignore new tick\n if (!data.kind.toLowerCase().includes(\"new_tick\")) {\n const new_event: EventProp = {\n id: this.event_id,\n name: data.kind,\n tick: data.tick\n }\n\n this.event_id += 1\n\n this.setState((state) => ({\n events: [...state.events, new_event]\n }))\n }\n })\n }\n\n render() {\n return (\n <>\n \n Rustico\n - {String(this.state.current_price.toLocaleString())} {String(this.state.active_pair.base) + \"/\" + String(this.state.active_pair.quote)} \n \n
    \n
    \n \n\n \n
    \n \n
    \n\n
    \n \n \n
    \n
    \n\n {this.state.positions.length > 0 ?\n : null}\n\n
    \n Made with ❤️ by the Peperone in a scantinato\n
    \n \n\n \n
    \n
    \n \n )\n }\n}\n\nexport default App;","/home/giulio/dev/gkaching/websrc/src/components/Cards.tsx",["22","23","24","25"],"import React, { Component } from 'react';\nimport { Balance, EventName, FirstConnectMessage, NewTickMessage } from \"../types\";\nimport { socket } from \"../index\";\n\nexport type CoinBalanceProps = {\n name: string,\n amount: number,\n percentage: number,\n quote_equivalent: number,\n quote_symbol: string,\n}\n\nclass CoinBalance extends Component {\n constructor(props: CoinBalanceProps) {\n super(props);\n }\n\n render() {\n // do not print equivalent if this element is the quote itself\n const quoteBlock = this.props.name != this.props.quote_symbol ? this.props.quote_symbol.concat(\" \").concat(this.props.quote_equivalent.toLocaleString()) : null\n\n // const accessory = SymbolAccessories.filter((accessory) => {\n // return accessory.name == this.props.name\n // })\n //\n // const icon = accessory.length > 0 ? accessory.pop().icon : null\n\n return (\n
    \n
    \n {/*{icon}*/}\n
    \n
    \n {this.props.name}\n
    \n
    \n
    \n {Math.trunc(this.props.percentage)}%\n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n {this.props.amount.toFixed(5)} {this.props.name}\n
    \n
    \n
    \n \n {quoteBlock}\n
    \n
    \n
    \n
    \n )\n }\n\n}\n\nexport type WalletCardProps = {\n quote: string,\n}\n\nexport class WalletCard extends Component\n <{}, { balances: Array }> {\n // constructor(props) {\n // super(props);\n // }\n\n state =\n {\n balances: []\n }\n\n totalQuoteBalance() {\n let total = 0\n\n this.state.balances.forEach((balance: Balance) => {\n if (balance.currency == balance.quote) {\n total += balance.amount\n } else {\n total += balance.quote_equivalent\n }\n })\n\n return total\n }\n\n renderCoinBalances() {\n return (\n this.state.balances.map((balance: Balance) => {\n const percentage_amount = balance.quote == balance.currency ? balance.amount : balance.quote_equivalent;\n\n return (\n \n )\n })\n )\n }\n\n componentDidMount() {\n socket.on(EventName.NewTick, (data: NewTickMessage) => {\n this.setState({\n balances: data.balances\n })\n })\n\n socket.on(EventName.FirstConnect, (data: FirstConnectMessage) => {\n this.setState({\n balances: data.balances\n })\n })\n }\n\n render() {\n return (\n
    \n \n
    \n
    \n

    Your Wallets

    \n
    \n \n \n
    \n
    \n
    \n\n {this.renderCoinBalances()}\n\n
    \n
    \n Total Balance ≈ USD {this.totalQuoteBalance().toLocaleString()}\n
    \n
    \n
    \n
    \n )\n }\n}","/home/giulio/dev/gkaching/websrc/src/components/Overlays.tsx",["26"],"import React, {Component} from \"react\";\nimport {socket} from \"../index\";\nimport {EventName} from \"../types\";\n\nexport type ModalProps = {\n show: boolean,\n positionId: number,\n toggleConfirmation: any\n}\n\nexport class ClosePositionModal extends Component {\n constructor(props: ModalProps) {\n super(props);\n }\n\n render() {\n if (!this.props.show) {\n return null\n }\n\n return (\n
    \n
    \n
    \n
    \n
    \n\n {/*This element is to trick the browser into centering the modal contents. -->*/}\n \n\n {/*Modal panel, show/hide based on modal state.*/}\n\n {/*Entering: \"ease-out duration-300\"*/}\n {/* From: \"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95\"*/}\n {/* To: \"opacity-100 translate-y-0 sm:scale-100\"*/}\n {/*Leaving: \"ease-in duration-200\"*/}\n {/* From: \"opacity-100 translate-y-0 sm:scale-100\"*/}\n {/* To: \"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95\"*/}\n\n \n
    \n
    \n \n {/*Heroicon name: exclamation -->*/}\n \n \n \n
    \n
    \n

    \n Close position\n

    \n
    \n

    \n Are you sure you want to close the position? This action cannot be undone.\n

    \n
    \n
    \n
    \n
    \n
    \n \n \n
    \n
    \n
    \n
    \n )\n }\n}","/home/giulio/dev/gkaching/websrc/src/index.tsx",[],{"ruleId":"27","severity":1,"message":"28","line":45,"column":5,"nodeType":"29","messageId":"30","endLine":47,"endColumn":6},{"ruleId":"27","severity":1,"message":"28","line":14,"column":5,"nodeType":"29","messageId":"30","endLine":16,"endColumn":6},{"ruleId":"31","severity":1,"message":"32","line":20,"column":44,"nodeType":"33","messageId":"34","endLine":20,"endColumn":46},{"ruleId":"31","severity":1,"message":"35","line":83,"column":34,"nodeType":"33","messageId":"34","endLine":83,"endColumn":36},{"ruleId":"31","severity":1,"message":"35","line":96,"column":57,"nodeType":"33","messageId":"34","endLine":96,"endColumn":59},{"ruleId":"27","severity":1,"message":"28","line":12,"column":5,"nodeType":"29","messageId":"30","endLine":14,"endColumn":6},"@typescript-eslint/no-useless-constructor","Useless constructor.","MethodDefinition","noUselessConstructor","eqeqeq","Expected '!==' and instead saw '!='.","BinaryExpression","unexpected","Expected '===' and instead saw '=='."] \ No newline at end of file +[{"/home/giulio/dev/gkaching/websrc/src/components/App.tsx":"1","/home/giulio/dev/gkaching/websrc/src/components/Cards.tsx":"2","/home/giulio/dev/gkaching/websrc/src/components/Overlays.tsx":"3","/home/giulio/dev/gkaching/websrc/src/index.tsx":"4"},{"size":4418,"mtime":1609342346604,"results":"5","hashOfConfig":"6"},{"size":5688,"mtime":1609342346604,"results":"7","hashOfConfig":"6"},{"size":5235,"mtime":1609331232067,"results":"8","hashOfConfig":"6"},{"size":321,"mtime":1609342346604,"results":"9","hashOfConfig":"6"},{"filePath":"10","messages":"11","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},"1ev2e5",{"filePath":"12","messages":"13","errorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"14","messages":"15","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"16"},{"filePath":"17","messages":"18","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/home/giulio/dev/gkaching/websrc/src/components/App.tsx",["19"],"/home/giulio/dev/gkaching/websrc/src/components/Cards.tsx",["20","21","22","23"],"/home/giulio/dev/gkaching/websrc/src/components/Overlays.tsx",["24"],"import React, {Component} from \"react\";\nimport {socket} from \"../index\";\nimport {EventName} from \"../types\";\n\nexport type ModalProps = {\n show: boolean,\n positionId: number,\n toggleConfirmation: any\n}\n\nexport class ClosePositionModal extends Component {\n constructor(props: ModalProps) {\n super(props);\n }\n\n render() {\n if (!this.props.show) {\n return null\n }\n\n return (\n
    \n
    \n
    \n
    \n
    \n\n {/*This element is to trick the browser into centering the modal contents. -->*/}\n \n\n {/*Modal panel, show/hide based on modal state.*/}\n\n {/*Entering: \"ease-out duration-300\"*/}\n {/* From: \"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95\"*/}\n {/* To: \"opacity-100 translate-y-0 sm:scale-100\"*/}\n {/*Leaving: \"ease-in duration-200\"*/}\n {/* From: \"opacity-100 translate-y-0 sm:scale-100\"*/}\n {/* To: \"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95\"*/}\n\n \n
    \n
    \n \n {/*Heroicon name: exclamation -->*/}\n \n \n \n
    \n
    \n

    \n Close position\n

    \n
    \n

    \n Are you sure you want to close the position? This action cannot be undone.\n

    \n
    \n
    \n
    \n
    \n
    \n \n \n
    \n
    \n
    \n \n )\n }\n}","/home/giulio/dev/gkaching/websrc/src/index.tsx",[],{"ruleId":"25","severity":1,"message":"26","line":45,"column":5,"nodeType":"27","messageId":"28","endLine":47,"endColumn":6},{"ruleId":"25","severity":1,"message":"26","line":14,"column":5,"nodeType":"27","messageId":"28","endLine":16,"endColumn":6},{"ruleId":"29","severity":1,"message":"30","line":20,"column":44,"nodeType":"31","messageId":"32","endLine":20,"endColumn":46},{"ruleId":"29","severity":1,"message":"33","line":83,"column":34,"nodeType":"31","messageId":"32","endLine":83,"endColumn":36},{"ruleId":"29","severity":1,"message":"33","line":96,"column":57,"nodeType":"31","messageId":"32","endLine":96,"endColumn":59},{"ruleId":"25","severity":1,"message":"26","line":12,"column":5,"nodeType":"27","messageId":"28","endLine":14,"endColumn":6},"@typescript-eslint/no-useless-constructor","Useless constructor.","MethodDefinition","noUselessConstructor","eqeqeq","Expected '!==' and instead saw '!='.","BinaryExpression","unexpected","Expected '===' and instead saw '=='."] \ No newline at end of file -- 2.47.2 From 6b97847882ff5b7be54ba13ab751b367ed738ca2 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Fri, 1 Jan 2021 14:07:16 +0000 Subject: [PATCH 004/127] initial stub --- rustybot/Cargo.lock | 1622 ++++++++++++++++++++++++++++++++++++++++++ rustybot/Cargo.toml | 13 + rustybot/src/main.rs | 35 + websrc/.eslintcache | 2 +- websrc/src/index.tsx | 2 + 5 files changed, 1673 insertions(+), 1 deletion(-) create mode 100644 rustybot/Cargo.lock create mode 100644 rustybot/Cargo.toml create mode 100644 rustybot/src/main.rs diff --git a/rustybot/Cargo.lock b/rustybot/Cargo.lock new file mode 100644 index 0000000..a10ac48 --- /dev/null +++ b/rustybot/Cargo.lock @@ -0,0 +1,1622 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "addr2line" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "backtrace" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5140344c85b01f9bbb4d4b7288a8aa4b3287ccef913a14bcc78a1063623598" +dependencies = [ + "addr2line", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitfinex" +version = "0.4.3" +dependencies = [ + "error-chain", + "hex", + "log 0.3.9", + "reqwest", + "ring", + "serde", + "serde_derive", + "serde_json", + "tungstenite 0.9.2", + "url", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array 0.12.3", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "bumpalo" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +dependencies = [ + "byteorder", + "iovec", +] + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "bytes" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0dcbc35f504eb6fc275a6d20e4ebcda18cf50d40ba6fabff8c711fa16cb3b16" + +[[package]] +name = "cc" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "core-foundation" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" + +[[package]] +name = "cpuid-bool" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.3", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "encoding_rs" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801bbab217d7f79c0062f4f7205b5d4427c6d1a7bd7aafdd1475f7c59d62b283" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "backtrace", + "version_check", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures-channel" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" + +[[package]] +name = "futures-sink" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d" + +[[package]] +name = "futures-task" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d" +dependencies = [ + "once_cell", +] + +[[package]] +name = "futures-util" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project 1.0.2", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" + +[[package]] +name = "h2" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535" +dependencies = [ + "bytes 0.5.6", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.2", + "indexmap", + "slab", + "tokio 0.2.24", + "tokio-util", + "tracing", + "tracing-futures", +] + +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" + +[[package]] +name = "hermit-abi" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" + +[[package]] +name = "http" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0" +dependencies = [ + "bytes 0.4.12", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84129d298a6d57d246960ff8eb831ca4af3f96d29e2e28848dae275408658e26" +dependencies = [ + "bytes 0.5.6", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" +dependencies = [ + "bytes 0.5.6", + "http 0.2.2", +] + +[[package]] +name = "httparse" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" + +[[package]] +name = "httpdate" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" + +[[package]] +name = "hyper" +version = "0.13.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ad767baac13b44d4529fcf58ba2cd0995e36e7b435bc5b039de6f47e880dbf" +dependencies = [ + "bytes 0.5.6", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.2", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project 1.0.2", + "socket2", + "tokio 0.2.24", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d979acc56dcb5b8dddba3917601745e877576475aa046df3226eabdecef78eed" +dependencies = [ + "bytes 0.5.6", + "hyper", + "native-tls", + "tokio 0.2.24", + "tokio-tls", +] + +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "input_buffer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1b822cc844905551931d6f81608ed5f50a79c1078a4e2b4d42dbc7c1eedfbf" +dependencies = [ + "bytes 0.4.12", +] + +[[package]] +name = "input_buffer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" +dependencies = [ + "bytes 0.5.6", +] + +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "ipnet" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "js-sys" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" + +[[package]] +name = "lock_api" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.11", +] + +[[package]] +name = "log" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +dependencies = [ + "cfg-if 0.1.10", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log 0.4.11", + "miow 0.2.2", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7" +dependencies = [ + "libc", + "log 0.4.11", + "miow 0.3.6", + "ntapi", + "winapi 0.3.9", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "miow" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" +dependencies = [ + "socket2", + "winapi 0.3.9", +] + +[[package]] +name = "native-tls" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" +dependencies = [ + "lazy_static", + "libc", + "log 0.4.11", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "net2" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397" + +[[package]] +name = "once_cell" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038d43985d1ddca7a9900630d8cd031b56e4794eecc2e9ea39dd17aa04399a70" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "lazy_static", + "libc", + "openssl-sys", +] + +[[package]] +name = "openssl-probe" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" + +[[package]] +name = "openssl-sys" +version = "0.9.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "921fc71883267538946025deffb622905ecad223c28efbfdef9bb59a0175f3e6" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi 0.3.9", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15" +dependencies = [ + "pin-project-internal 0.4.27", +] + +[[package]] +name = "pin-project" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7" +dependencies = [ + "pin-project-internal 1.0.2", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" + +[[package]] +name = "pin-project-lite" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "reqwest" +version = "0.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0718f81a8e14c4dbb3b34cf23dc6aaf9ab8a0dfec160c534b3dbca1aaa21f47c" +dependencies = [ + "base64 0.13.0", + "bytes 0.5.6", + "encoding_rs", + "futures-core", + "futures-util", + "http 0.2.2", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "lazy_static", + "log 0.4.11", + "mime", + "mime_guess", + "native-tls", + "percent-encoding", + "pin-project-lite 0.2.0", + "serde", + "serde_urlencoded", + "tokio 0.2.24", + "tokio-tls", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "ring" +version = "0.16.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "024a1e66fea74c66c66624ee5622a7ff0e4b73a13b4f5c326ddb50c708944226" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi 0.3.9", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" + +[[package]] +name = "rustybot" +version = "0.1.0" +dependencies = [ + "bitfinex", + "futures-util", + "tokio 0.3.6", + "tokio-tungstenite", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi 0.3.9", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "security-framework" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1759c2e3c8580017a484a7ac56d3abc5a6c1feadf88db2f3633f12ae4268c69" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f99b9d5e26d2a71633cc4f2ebae7cc9f874044e0c351a27e17892d76dce5678b" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" + +[[package]] +name = "serde_derive" +version = "1.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha-1" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpuid-bool", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "signal-hook-registry" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "smallvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" + +[[package]] +name = "socket2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "syn" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9802ddde94170d186eeee5005b798d9c159fa970403f1be19976d0cfb939b72" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.9", +] + +[[package]] +name = "tinyvec" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099837d3464c16a808060bb3f02263b412f6fafcb5d01c533d309985fbeebe48" +dependencies = [ + "bytes 0.5.6", + "fnv", + "futures-core", + "iovec", + "lazy_static", + "memchr", + "mio 0.6.23", + "pin-project-lite 0.1.11", + "slab", +] + +[[package]] +name = "tokio" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "720ba21c25078711bf456d607987d95bce90f7c3bea5abe1db587862e7a1e87c" +dependencies = [ + "autocfg", + "bytes 0.6.0", + "futures-core", + "libc", + "memchr", + "mio 0.7.7", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite 0.2.0", + "signal-hook-registry", + "slab", + "tokio-macros", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-macros" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46dfffa59fc3c8aad216ed61bdc2c263d2b9d87a9c8ac9de0c11a813e51b6db7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" +dependencies = [ + "native-tls", + "tokio 0.2.24", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0381c1e6e08908317cee104781ca48afe03f37cc857792b85f01f9828fb55ba3" +dependencies = [ + "futures-util", + "log 0.4.11", + "pin-project 1.0.2", + "tokio 0.3.6", + "tungstenite 0.11.1", +] + +[[package]] +name = "tokio-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +dependencies = [ + "bytes 0.5.6", + "futures-core", + "futures-sink", + "log 0.4.11", + "pin-project-lite 0.1.11", + "tokio 0.2.24", +] + +[[package]] +name = "tower-service" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" + +[[package]] +name = "tracing" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3" +dependencies = [ + "cfg-if 1.0.0", + "log 0.4.11", + "pin-project-lite 0.2.0", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tracing-futures" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c" +dependencies = [ + "pin-project 0.4.27", + "tracing", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tungstenite" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0c2bd5aeb7dcd2bb32e472c8872759308495e5eccc942e929a513cd8d36110" +dependencies = [ + "base64 0.11.0", + "byteorder", + "bytes 0.4.12", + "http 0.1.21", + "httparse", + "input_buffer 0.2.0", + "log 0.4.11", + "native-tls", + "rand", + "sha-1 0.8.2", + "url", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0308d80d86700c5878b9ef6321f020f29b1bb9d5ff3cab25e75e23f3a492a23" +dependencies = [ + "base64 0.12.3", + "byteorder", + "bytes 0.5.6", + "http 0.2.2", + "httparse", + "input_buffer 0.3.1", + "log 0.4.11", + "rand", + "sha-1 0.9.2", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" + +[[package]] +name = "vcpkg" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log 0.4.11", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasm-bindgen" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e" +dependencies = [ + "cfg-if 1.0.0", + "serde", + "serde_json", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62" +dependencies = [ + "bumpalo", + "lazy_static", + "log 0.4.11", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe9756085a84584ee9457a002b7cdfe0bfff169f45d2591d8be1345a6780e35" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158" + +[[package]] +name = "web-sys" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] diff --git a/rustybot/Cargo.toml b/rustybot/Cargo.toml new file mode 100644 index 0000000..511dbcf --- /dev/null +++ b/rustybot/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rustybot" +version = "0.1.0" +authors = ["Giulio De Pasquale "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bitfinex = { path= "/home/giulio/gitstuff/bitfinex-rs" } +tokio = { version = "0.3", features=["full"]} +tokio-tungstenite = "*" +futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] } \ No newline at end of file diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs new file mode 100644 index 0000000..857b298 --- /dev/null +++ b/rustybot/src/main.rs @@ -0,0 +1,35 @@ +use std::env; +use std::net::SocketAddr; + +use futures_util::StreamExt; +use tokio::net::{TcpListener, TcpStream}; + +pub type BoxError = Box; + +async fn accept_connection(stream: TcpStream, addr: SocketAddr) -> Result<(), BoxError> { + println!("Peer address: {}", addr); + + + let ws_stream = tokio_tungstenite::accept_async(stream) + .await + .err(format!("Error during WS handshake.").into()); + + println!("New WebSocket connection: {}", addr); + + let (write, read) = ws_stream.split(); + read.forward(write).await.expect("Failed to forward message") +} + +#[tokio::main] +async fn main() -> Result<(), BoxError> { + // Create the event loop and TCP listener we'll accept connections on. + let addr = env::args().nth(1).unwrap_or_else(|| "127.0.0.1:8080".to_string()); + let try_socket = TcpListener::bind(&addr).await; + let mut listener = try_socket.expect("Failed to bind"); + + while let Ok((stream, addr)) = listener.accept().await { + tokio::spawn(accept_connection(stream, addr)); + } + + Ok(()) +} diff --git a/websrc/.eslintcache b/websrc/.eslintcache index d1449c5..0e3c44c 100644 --- a/websrc/.eslintcache +++ b/websrc/.eslintcache @@ -1 +1 @@ -[{"/home/giulio/dev/gkaching/websrc/src/components/App.tsx":"1","/home/giulio/dev/gkaching/websrc/src/components/Cards.tsx":"2","/home/giulio/dev/gkaching/websrc/src/components/Overlays.tsx":"3","/home/giulio/dev/gkaching/websrc/src/index.tsx":"4"},{"size":4418,"mtime":1609342346604,"results":"5","hashOfConfig":"6"},{"size":5688,"mtime":1609342346604,"results":"7","hashOfConfig":"6"},{"size":5235,"mtime":1609331232067,"results":"8","hashOfConfig":"6"},{"size":321,"mtime":1609342346604,"results":"9","hashOfConfig":"6"},{"filePath":"10","messages":"11","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},"1ev2e5",{"filePath":"12","messages":"13","errorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"14","messages":"15","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"16"},{"filePath":"17","messages":"18","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/home/giulio/dev/gkaching/websrc/src/components/App.tsx",["19"],"/home/giulio/dev/gkaching/websrc/src/components/Cards.tsx",["20","21","22","23"],"/home/giulio/dev/gkaching/websrc/src/components/Overlays.tsx",["24"],"import React, {Component} from \"react\";\nimport {socket} from \"../index\";\nimport {EventName} from \"../types\";\n\nexport type ModalProps = {\n show: boolean,\n positionId: number,\n toggleConfirmation: any\n}\n\nexport class ClosePositionModal extends Component {\n constructor(props: ModalProps) {\n super(props);\n }\n\n render() {\n if (!this.props.show) {\n return null\n }\n\n return (\n
    \n
    \n
    \n
    \n
    \n\n {/*This element is to trick the browser into centering the modal contents. -->*/}\n \n\n {/*Modal panel, show/hide based on modal state.*/}\n\n {/*Entering: \"ease-out duration-300\"*/}\n {/* From: \"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95\"*/}\n {/* To: \"opacity-100 translate-y-0 sm:scale-100\"*/}\n {/*Leaving: \"ease-in duration-200\"*/}\n {/* From: \"opacity-100 translate-y-0 sm:scale-100\"*/}\n {/* To: \"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95\"*/}\n\n \n
    \n
    \n \n {/*Heroicon name: exclamation -->*/}\n \n \n \n
    \n
    \n

    \n Close position\n

    \n
    \n

    \n Are you sure you want to close the position? This action cannot be undone.\n

    \n
    \n
    \n
    \n
    \n
    \n \n \n
    \n
    \n
    \n \n )\n }\n}","/home/giulio/dev/gkaching/websrc/src/index.tsx",[],{"ruleId":"25","severity":1,"message":"26","line":45,"column":5,"nodeType":"27","messageId":"28","endLine":47,"endColumn":6},{"ruleId":"25","severity":1,"message":"26","line":14,"column":5,"nodeType":"27","messageId":"28","endLine":16,"endColumn":6},{"ruleId":"29","severity":1,"message":"30","line":20,"column":44,"nodeType":"31","messageId":"32","endLine":20,"endColumn":46},{"ruleId":"29","severity":1,"message":"33","line":83,"column":34,"nodeType":"31","messageId":"32","endLine":83,"endColumn":36},{"ruleId":"29","severity":1,"message":"33","line":96,"column":57,"nodeType":"31","messageId":"32","endLine":96,"endColumn":59},{"ruleId":"25","severity":1,"message":"26","line":12,"column":5,"nodeType":"27","messageId":"28","endLine":14,"endColumn":6},"@typescript-eslint/no-useless-constructor","Useless constructor.","MethodDefinition","noUselessConstructor","eqeqeq","Expected '!==' and instead saw '!='.","BinaryExpression","unexpected","Expected '===' and instead saw '=='."] \ No newline at end of file +[{"/home/giulio/dev/gkaching/websrc/src/components/App.tsx":"1","/home/giulio/dev/gkaching/websrc/src/components/Cards.tsx":"2","/home/giulio/dev/gkaching/websrc/src/components/Overlays.tsx":"3","/home/giulio/dev/gkaching/websrc/src/index.tsx":"4"},{"size":4418,"mtime":1609342346604,"results":"5","hashOfConfig":"6"},{"size":5688,"mtime":1609342346604,"results":"7","hashOfConfig":"6"},{"size":5235,"mtime":1609342346604,"results":"8","hashOfConfig":"6"},{"size":371,"mtime":1609358781834,"results":"9","hashOfConfig":"6"},{"filePath":"10","messages":"11","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"12"},"1ev2e5",{"filePath":"13","messages":"14","errorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"15"},{"filePath":"16","messages":"17","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"18","messages":"19","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},"/home/giulio/dev/gkaching/websrc/src/components/App.tsx",["20"],"import React, { Component } from \"react\";\nimport {\n Balance,\n CurrencyPair,\n EventName,\n EventProp,\n FirstConnectMessage,\n NewEventMessage,\n NewTickMessage,\n PositionProp\n} from \"../types\";\nimport { socket } from \"../index\";\nimport { symbolToPair } from \"../utils\";\nimport { Helmet } from \"react-helmet\";\nimport { Navbar, Sidebar } from \"./Navbars\";\nimport { Statusbar } from \"./Statusbar\";\nimport { PositionsTable } from \"./Tables\";\nimport RPlot from \"./RPlot\";\n\ntype AppState = {\n current_price: number,\n current_tick: number,\n last_update: Date,\n positions: Array,\n events: Array,\n active_pair: CurrencyPair,\n available_pairs: Array,\n balances: Array\n}\n\nclass App extends Component<{}, AppState> {\n event_id = 0;\n\n state = {\n current_price: 0,\n current_tick: 0,\n last_update: new Date(),\n positions: [],\n events: [],\n balances: [],\n active_pair: symbolToPair(\"tBTCUSD\"),\n available_pairs: []\n }\n\n constructor(props: {}) {\n super(props)\n }\n\n componentDidMount() {\n socket.on(EventName.FirstConnect, (data: FirstConnectMessage) => {\n this.setState({\n current_price: data.prices[data.prices.length - 1],\n current_tick: data.ticks[data.ticks.length - 1],\n last_update: new Date(),\n positions: data.positions,\n balances: data.balances\n })\n })\n\n socket.on(EventName.NewTick, (data: NewTickMessage) => {\n this.setState({\n current_price: data.price,\n current_tick: data.tick,\n last_update: new Date(),\n positions: data.positions,\n balances: data.balances\n })\n })\n\n socket.on(EventName.NewEvent, (data: NewEventMessage) => {\n // ignore new tick\n if (!data.kind.toLowerCase().includes(\"new_tick\")) {\n const new_event: EventProp = {\n id: this.event_id,\n name: data.kind,\n tick: data.tick\n }\n\n this.event_id += 1\n\n this.setState((state) => ({\n events: [...state.events, new_event]\n }))\n }\n })\n }\n\n render() {\n return (\n <>\n \n Rustico\n - {String(this.state.current_price.toLocaleString())} {String(this.state.active_pair.base) + \"/\" + String(this.state.active_pair.quote)} \n \n
    \n
    \n \n\n \n
    \n \n
    \n\n
    \n \n \n
    \n
    \n\n {this.state.positions.length > 0 ?\n : null}\n\n
    \n Made with ❤️ by the Peperone in a scantinato\n
    \n \n\n \n
    \n \n \n )\n }\n}\n\nexport default App;","/home/giulio/dev/gkaching/websrc/src/components/Cards.tsx",["21","22","23","24"],"import React, { Component } from 'react';\nimport { Balance, EventName, FirstConnectMessage, NewTickMessage } from \"../types\";\nimport { socket } from \"../index\";\n\nexport type CoinBalanceProps = {\n name: string,\n amount: number,\n percentage: number,\n quote_equivalent: number,\n quote_symbol: string,\n}\n\nclass CoinBalance extends Component {\n constructor(props: CoinBalanceProps) {\n super(props);\n }\n\n render() {\n // do not print equivalent if this element is the quote itself\n const quoteBlock = this.props.name != this.props.quote_symbol ? this.props.quote_symbol.concat(\" \").concat(this.props.quote_equivalent.toLocaleString()) : null\n\n // const accessory = SymbolAccessories.filter((accessory) => {\n // return accessory.name == this.props.name\n // })\n //\n // const icon = accessory.length > 0 ? accessory.pop().icon : null\n\n return (\n
    \n
    \n {/*{icon}*/}\n
    \n
    \n {this.props.name}\n
    \n
    \n
    \n {Math.trunc(this.props.percentage)}%\n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n {this.props.amount.toFixed(5)} {this.props.name}\n
    \n
    \n
    \n \n {quoteBlock}\n
    \n
    \n
    \n
    \n )\n }\n\n}\n\nexport type WalletCardProps = {\n quote: string,\n}\n\nexport class WalletCard extends Component\n <{}, { balances: Array }> {\n // constructor(props) {\n // super(props);\n // }\n\n state =\n {\n balances: []\n }\n\n totalQuoteBalance() {\n let total = 0\n\n this.state.balances.forEach((balance: Balance) => {\n if (balance.currency == balance.quote) {\n total += balance.amount\n } else {\n total += balance.quote_equivalent\n }\n })\n\n return total\n }\n\n renderCoinBalances() {\n return (\n this.state.balances.map((balance: Balance) => {\n const percentage_amount = balance.quote == balance.currency ? balance.amount : balance.quote_equivalent;\n\n return (\n \n )\n })\n )\n }\n\n componentDidMount() {\n socket.on(EventName.NewTick, (data: NewTickMessage) => {\n this.setState({\n balances: data.balances\n })\n })\n\n socket.on(EventName.FirstConnect, (data: FirstConnectMessage) => {\n this.setState({\n balances: data.balances\n })\n })\n }\n\n render() {\n return (\n
    \n \n
    \n
    \n

    Your Wallets

    \n
    \n \n \n
    \n
    \n
    \n\n {this.renderCoinBalances()}\n\n
    \n
    \n Total Balance ≈ USD {this.totalQuoteBalance().toLocaleString()}\n
    \n
    \n
    \n \n )\n }\n}","/home/giulio/dev/gkaching/websrc/src/components/Overlays.tsx",["25"],"/home/giulio/dev/gkaching/websrc/src/index.tsx",["26"],{"ruleId":"27","severity":1,"message":"28","line":45,"column":5,"nodeType":"29","messageId":"30","endLine":47,"endColumn":6},{"ruleId":"27","severity":1,"message":"28","line":14,"column":5,"nodeType":"29","messageId":"30","endLine":16,"endColumn":6},{"ruleId":"31","severity":1,"message":"32","line":20,"column":44,"nodeType":"33","messageId":"34","endLine":20,"endColumn":46},{"ruleId":"31","severity":1,"message":"35","line":83,"column":34,"nodeType":"33","messageId":"34","endLine":83,"endColumn":36},{"ruleId":"31","severity":1,"message":"35","line":96,"column":57,"nodeType":"33","messageId":"34","endLine":96,"endColumn":59},{"ruleId":"27","severity":1,"message":"28","line":12,"column":5,"nodeType":"29","messageId":"30","endLine":14,"endColumn":6},{"ruleId":"36","severity":1,"message":"37","line":13,"column":7,"nodeType":"38","messageId":"39","endLine":13,"endColumn":9},"@typescript-eslint/no-useless-constructor","Useless constructor.","MethodDefinition","noUselessConstructor","eqeqeq","Expected '!==' and instead saw '!='.","BinaryExpression","unexpected","Expected '===' and instead saw '=='.","@typescript-eslint/no-unused-vars","'ws' is assigned a value but never used.","Identifier","unusedVar"] \ No newline at end of file diff --git a/websrc/src/index.tsx b/websrc/src/index.tsx index 56b8b2c..98c1e96 100644 --- a/websrc/src/index.tsx +++ b/websrc/src/index.tsx @@ -10,4 +10,6 @@ socket.on("connect", function () { console.log("Connected!") }) +const ws = new WebSocket('ws://localhost:8080'); + ReactDOM.render(, document.getElementById("root")); -- 2.47.2 From 2705e393df6c60e9de4a2a6c46c70910a712eda5 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sat, 2 Jan 2021 12:15:19 +0000 Subject: [PATCH 005/127] stuff --- rustybot/Cargo.toml | 2 +- rustybot/src/main.rs | 31 ++++--------------------------- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/rustybot/Cargo.toml b/rustybot/Cargo.toml index 511dbcf..c4e7e80 100644 --- a/rustybot/Cargo.toml +++ b/rustybot/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bitfinex = { path= "/home/giulio/gitstuff/bitfinex-rs" } +bitfinex = { path= "/home/giulio/dev/bitfinex-rs" } tokio = { version = "0.3", features=["full"]} tokio-tungstenite = "*" futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] } \ No newline at end of file diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index 857b298..bfc351e 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -1,35 +1,12 @@ -use std::env; -use std::net::SocketAddr; +mod ticker; +mod positions; +mod events; +mod pairs; -use futures_util::StreamExt; -use tokio::net::{TcpListener, TcpStream}; pub type BoxError = Box; -async fn accept_connection(stream: TcpStream, addr: SocketAddr) -> Result<(), BoxError> { - println!("Peer address: {}", addr); - - - let ws_stream = tokio_tungstenite::accept_async(stream) - .await - .err(format!("Error during WS handshake.").into()); - - println!("New WebSocket connection: {}", addr); - - let (write, read) = ws_stream.split(); - read.forward(write).await.expect("Failed to forward message") -} - #[tokio::main] async fn main() -> Result<(), BoxError> { - // Create the event loop and TCP listener we'll accept connections on. - let addr = env::args().nth(1).unwrap_or_else(|| "127.0.0.1:8080".to_string()); - let try_socket = TcpListener::bind(&addr).await; - let mut listener = try_socket.expect("Failed to bind"); - - while let Ok((stream, addr)) = listener.accept().await { - tokio::spawn(accept_connection(stream, addr)); - } - Ok(()) } -- 2.47.2 From 370a57dbb9c6a1955f4c0bdf9438646020ffa24e Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sat, 2 Jan 2021 12:16:48 +0000 Subject: [PATCH 006/127] stuff --- rustybot/src/events.rs | 70 +++++++++++++++++++++++++++++++++++++++ rustybot/src/pairs.rs | 3 ++ rustybot/src/positions.rs | 29 ++++++++++++++++ rustybot/src/ticker.rs | 21 ++++++++++++ 4 files changed, 123 insertions(+) create mode 100644 rustybot/src/events.rs create mode 100644 rustybot/src/pairs.rs create mode 100644 rustybot/src/positions.rs create mode 100644 rustybot/src/ticker.rs diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs new file mode 100644 index 0000000..52fae85 --- /dev/null +++ b/rustybot/src/events.rs @@ -0,0 +1,70 @@ +use std::collections::HashMap; +use std::future::Future; + +use tokio::task::JoinHandle; +use crate::BoxError; + +enum SignalKind { + ClosePosition, + OpenPosition, +} + +struct EventMetadata { + position_id: Option, + order_id: Option, +} + +#[derive(PartialEq, Eq, Hash)] +enum EventKind { + NewMinimum, + NewMaximum, + ReachedLoss, + ReachedBreakEven, + ReachedMinProfit, + ReachedGoodProfit, + ReachedMaxLoss, + TrailingStopSet, + TrailingStopMoved, + OrderSubmitted, + NewTick, +} + +struct Event { + kind: EventKind, + tick: u64, + metadata: Option, +} + +impl Event { + pub fn new(kind: EventKind, tick: u64, metadata: Option) -> Self { + Event { + kind, + tick, + metadata, + } + } + + fn has_metadata(&self) -> bool { + self.metadata.is_some() + } +} + +pub struct Dispatcher { + event_handlers: HashMap JoinHandle<()>>> +} + +impl Dispatcher { + pub fn new() -> Self { + Dispatcher { + event_handlers: HashMap::new() + } + } + + pub fn register_event_handler(&mut self, event: EventKind, mut f: F) + where + F: FnMut(String) -> Fut, + Fut: Future + Send, + { + self.event_handlers.insert(event, Box::new(move |args| tokio::spawn(f(args)))); + } +} \ No newline at end of file diff --git a/rustybot/src/pairs.rs b/rustybot/src/pairs.rs new file mode 100644 index 0000000..302c0bd --- /dev/null +++ b/rustybot/src/pairs.rs @@ -0,0 +1,3 @@ +struct PairStatus { + +} \ No newline at end of file diff --git a/rustybot/src/positions.rs b/rustybot/src/positions.rs new file mode 100644 index 0000000..d259cb3 --- /dev/null +++ b/rustybot/src/positions.rs @@ -0,0 +1,29 @@ +pub enum PositionState { + Critical, + Loss, + BreakEven, + MinimumProfit, + Profit, +} + +impl PositionState { + fn color(self) -> String { + match self { + PositionState::Critical | PositionState::Loss => { "red" } + PositionState::BreakEven => { "yellow" } + PositionState::MinimumProfit | PositionState::Profit => { "green" } + }.into() + } +} + +// TODO: implement position in bitfinex API before completing this struct +pub struct PositionWrapper { + position: String, + net_profit_loss: f64, + net_profit_loss_percentage: f64, + state: PositionState +} + + + + diff --git a/rustybot/src/ticker.rs b/rustybot/src/ticker.rs new file mode 100644 index 0000000..7bdf318 --- /dev/null +++ b/rustybot/src/ticker.rs @@ -0,0 +1,21 @@ +use tokio::time::{Duration, Instant}; + +pub struct Ticker { + duration: Duration, + start_time: Instant, + current_tick: u64, +} + +impl Ticker { + fn new(duration: Duration) -> Self { + Ticker { + duration, + start_time: Instant::now(), + current_tick: 1, + } + } + + fn inc(&mut self) { + self.current_tick += 1 + } +} -- 2.47.2 From d4e388154d672b86fa4e4efe0b4114c85a068cbb Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sat, 2 Jan 2021 14:10:16 +0000 Subject: [PATCH 007/127] stuff --- rustybot/Cargo.lock | 99 +++++++++++++-------------------------- rustybot/Cargo.toml | 2 +- rustybot/src/events.rs | 23 +++++---- rustybot/src/main.rs | 11 ++++- rustybot/src/pairs.rs | 29 +++++++++++- rustybot/src/positions.rs | 22 +++++++-- rustybot/src/strategy.rs | 8 ++++ 7 files changed, 112 insertions(+), 82 deletions(-) create mode 100644 rustybot/src/strategy.rs diff --git a/rustybot/Cargo.lock b/rustybot/Cargo.lock index a10ac48..a320dd2 100644 --- a/rustybot/Cargo.lock +++ b/rustybot/Cargo.lock @@ -516,15 +516,6 @@ dependencies = [ "bytes 0.5.6", ] -[[package]] -name = "instant" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" -dependencies = [ - "cfg-if 1.0.0", -] - [[package]] name = "iovec" version = "0.1.4" @@ -577,15 +568,6 @@ version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" -[[package]] -name = "lock_api" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" -dependencies = [ - "scopeguard", -] - [[package]] name = "log" version = "0.3.9" @@ -674,6 +656,29 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "mio-named-pipes" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656" +dependencies = [ + "log 0.4.11", + "mio 0.6.23", + "miow 0.3.6", + "winapi 0.3.9", +] + +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio 0.6.23", +] + [[package]] name = "miow" version = "0.2.2" @@ -801,31 +806,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "parking_lot" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272" -dependencies = [ - "cfg-if 1.0.0", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi 0.3.9", -] - [[package]] name = "percent-encoding" version = "2.1.0" @@ -1038,7 +1018,7 @@ version = "0.1.0" dependencies = [ "bitfinex", "futures-util", - "tokio 0.3.6", + "tokio 0.2.24", "tokio-tungstenite", ] @@ -1058,12 +1038,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - [[package]] name = "security-framework" version = "2.0.0" @@ -1167,12 +1141,6 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -[[package]] -name = "smallvec" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" - [[package]] name = "socket2" version = "0.3.19" @@ -1241,10 +1209,17 @@ dependencies = [ "futures-core", "iovec", "lazy_static", + "libc", "memchr", "mio 0.6.23", + "mio-named-pipes", + "mio-uds", + "num_cpus", "pin-project-lite 0.1.11", + "signal-hook-registry", "slab", + "tokio-macros", + "winapi 0.3.9", ] [[package]] @@ -1255,25 +1230,17 @@ checksum = "720ba21c25078711bf456d607987d95bce90f7c3bea5abe1db587862e7a1e87c" dependencies = [ "autocfg", "bytes 0.6.0", - "futures-core", "libc", "memchr", "mio 0.7.7", - "num_cpus", - "once_cell", - "parking_lot", "pin-project-lite 0.2.0", - "signal-hook-registry", - "slab", - "tokio-macros", - "winapi 0.3.9", ] [[package]] name = "tokio-macros" -version = "0.3.2" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46dfffa59fc3c8aad216ed61bdc2c263d2b9d87a9c8ac9de0c11a813e51b6db7" +checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" dependencies = [ "proc-macro2", "quote", diff --git a/rustybot/Cargo.toml b/rustybot/Cargo.toml index c4e7e80..06577b5 100644 --- a/rustybot/Cargo.toml +++ b/rustybot/Cargo.toml @@ -8,6 +8,6 @@ edition = "2018" [dependencies] bitfinex = { path= "/home/giulio/dev/bitfinex-rs" } -tokio = { version = "0.3", features=["full"]} +tokio = { version = "0.2", features=["full"]} tokio-tungstenite = "*" futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] } \ No newline at end of file diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 52fae85..0cd7eed 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::future::Future; use tokio::task::JoinHandle; + use crate::BoxError; enum SignalKind { @@ -29,7 +30,7 @@ enum EventKind { NewTick, } -struct Event { +pub struct Event { kind: EventKind, tick: u64, metadata: Option, @@ -49,21 +50,27 @@ impl Event { } } -pub struct Dispatcher { - event_handlers: HashMap JoinHandle<()>>> +pub struct EventDispatcher { + event_handlers: HashMap JoinHandle<()>>> } -impl Dispatcher { +impl EventDispatcher { pub fn new() -> Self { - Dispatcher { + EventDispatcher { event_handlers: HashMap::new() } } - pub fn register_event_handler(&mut self, event: EventKind, mut f: F) + pub fn call_handler(&self, event: &EventKind) { + self.event_handlers.iter() + .filter(|(e, _)| e == event) + .map(|(_, f)| f("test".into())); + } + + pub fn register_event_handler(&mut self, event: EventKind, f: F) where - F: FnMut(String) -> Fut, - Fut: Future + Send, + F: Fn(String) -> Fut, + Fut: Future + Send, { self.event_handlers.insert(event, Box::new(move |args| tokio::spawn(f(args)))); } diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index bfc351e..914da6e 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -1,12 +1,21 @@ +use bitfinex::api::Bitfinex; + mod ticker; -mod positions; mod events; mod pairs; +mod positions; +mod strategy; pub type BoxError = Box; #[tokio::main] async fn main() -> Result<(), BoxError> { + let TEST_API_KEY="P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2"; + let TEST_API_SECRET="1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli"; + + let bfx = Bitfinex::new(Some(TEST_API_KEY.into()), Some(TEST_API_SECRET.into())); + + println!("{:?}", bfx.positions.active_positions().await); Ok(()) } diff --git a/rustybot/src/pairs.rs b/rustybot/src/pairs.rs index 302c0bd..a3e834c 100644 --- a/rustybot/src/pairs.rs +++ b/rustybot/src/pairs.rs @@ -1,3 +1,30 @@ -struct PairStatus { +use std::collections::HashMap; +use crate::events::{EventDispatcher, Event}; +use crate::positions::PositionWrapper; +use crate::strategy::Strategy; + +pub struct PairStatus { + pair: String, + dispatcher: EventDispatcher, + prices: HashMap, + events: Vec, + // orders: HashMap>, + positions: HashMap>, + current_tick: u64, + strategy: Option>, +} + +impl PairStatus { + pub fn new(pair: String, current_tick: u64, strategy: Option>) -> Self { + PairStatus { + pair, + dispatcher: EventDispatcher::new(), + prices: HashMap::new(), + events: Vec::new(), + positions: HashMap::new(), + current_tick, + strategy, + } + } } \ No newline at end of file diff --git a/rustybot/src/positions.rs b/rustybot/src/positions.rs index d259cb3..4b228cc 100644 --- a/rustybot/src/positions.rs +++ b/rustybot/src/positions.rs @@ -1,3 +1,5 @@ +use bitfinex::positions::Position; + pub enum PositionState { Critical, Loss, @@ -16,12 +18,22 @@ impl PositionState { } } -// TODO: implement position in bitfinex API before completing this struct pub struct PositionWrapper { - position: String, - net_profit_loss: f64, - net_profit_loss_percentage: f64, - state: PositionState + position: Position, + net_pl: f64, + net_pl_perc: f64, + state: PositionState, +} + +impl PositionWrapper { + pub fn new(position: Position, net_pl: f64, net_pl_perc: f64, state: PositionState) -> Self { + PositionWrapper { + position, + net_pl, + net_pl_perc, + state, + } + } } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs new file mode 100644 index 0000000..b8acf7d --- /dev/null +++ b/rustybot/src/strategy.rs @@ -0,0 +1,8 @@ +use bitfinex::positions::Position; + +use crate::events::Event; +use crate::positions::PositionWrapper; + +pub trait Strategy { + fn position_on_new_tick(&self, position: Position) -> (PositionWrapper, Vec); +} \ No newline at end of file -- 2.47.2 From bf3da0723ff97f7d856acc7ebce7f0ffaef9e29b Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sat, 2 Jan 2021 15:22:48 +0000 Subject: [PATCH 008/127] dispatcher implemented --- rustybot/src/events.rs | 41 ++++++++++++++++++++++++--------------- rustybot/src/positions.rs | 2 ++ 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 0cd7eed..86a096f 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -1,22 +1,26 @@ use std::collections::HashMap; use std::future::Future; +use bitfinex::positions::Position; use tokio::task::JoinHandle; use crate::BoxError; +use crate::pairs::PairStatus; -enum SignalKind { +#[derive(Copy, Clone)] +pub enum SignalKind { ClosePosition, OpenPosition, } -struct EventMetadata { +#[derive(Copy, Clone)] +pub struct EventMetadata { position_id: Option, order_id: Option, } -#[derive(PartialEq, Eq, Hash)] -enum EventKind { +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub enum EventKind { NewMinimum, NewMaximum, ReachedLoss, @@ -30,6 +34,7 @@ enum EventKind { NewTick, } +#[derive(Copy, Clone)] pub struct Event { kind: EventKind, tick: u64, @@ -51,27 +56,31 @@ impl Event { } pub struct EventDispatcher { - event_handlers: HashMap JoinHandle<()>>> + event_handlers: HashMap JoinHandle<()>>>>, } impl EventDispatcher { pub fn new() -> Self { EventDispatcher { - event_handlers: HashMap::new() + event_handlers: HashMap::new(), } } - pub fn call_handler(&self, event: &EventKind) { - self.event_handlers.iter() - .filter(|(e, _)| e == event) - .map(|(_, f)| f("test".into())); + pub fn call_handler(&mut self, event: &EventKind, position: &Position, status: &PairStatus) { + if let Some(callbacks) = self.event_handlers.get_mut(event) { + for f in callbacks { + f(position, status); + } + } } - pub fn register_event_handler(&mut self, event: EventKind, f: F) + pub fn register_event_handler(&mut self, event: EventKind, mut f: F) where - F: Fn(String) -> Fut, - Fut: Future + Send, - { - self.event_handlers.insert(event, Box::new(move |args| tokio::spawn(f(args)))); + F: FnMut(&Position, &PairStatus) -> Fut, + Fut: Future + Send, { + self.event_handlers + .entry(event) + .or_default() + .push(Box::new(move |p, s| tokio::spawn(f(p, s)))); } -} \ No newline at end of file +} diff --git a/rustybot/src/positions.rs b/rustybot/src/positions.rs index 4b228cc..9284030 100644 --- a/rustybot/src/positions.rs +++ b/rustybot/src/positions.rs @@ -1,5 +1,6 @@ use bitfinex::positions::Position; +#[derive(Copy, Clone)] pub enum PositionState { Critical, Loss, @@ -18,6 +19,7 @@ impl PositionState { } } +#[derive(Clone)] pub struct PositionWrapper { position: Position, net_pl: f64, -- 2.47.2 From a03e0bc57482038a30e8780b3bd290cdf1e9e858 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sat, 2 Jan 2021 18:34:13 +0000 Subject: [PATCH 009/127] stuff --- rustybot/src/events.rs | 22 ++++++++++++++++------ rustybot/src/pairs.rs | 31 ++++++++++++++++++++++++++++++- rustybot/src/positions.rs | 4 ++-- 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 86a096f..2a7de09 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -53,10 +53,20 @@ impl Event { fn has_metadata(&self) -> bool { self.metadata.is_some() } + + pub fn kind(&self) -> EventKind { + self.kind + } + pub fn tick(&self) -> u64 { + self.tick + } + pub fn metadata(&self) -> Option { + self.metadata + } } pub struct EventDispatcher { - event_handlers: HashMap JoinHandle<()>>>>, + event_handlers: HashMap JoinHandle<()>>>>, } impl EventDispatcher { @@ -66,21 +76,21 @@ impl EventDispatcher { } } - pub fn call_handler(&mut self, event: &EventKind, position: &Position, status: &PairStatus) { - if let Some(callbacks) = self.event_handlers.get_mut(event) { + pub fn call_handler(&mut self, event: &Event, status: &PairStatus) { + if let Some(callbacks) = self.event_handlers.get_mut(&event.kind()) { for f in callbacks { - f(position, status); + f(event.clone(), status); } } } pub fn register_event_handler(&mut self, event: EventKind, mut f: F) where - F: FnMut(&Position, &PairStatus) -> Fut, + F: FnMut(Event, &PairStatus) -> Fut, Fut: Future + Send, { self.event_handlers .entry(event) .or_default() - .push(Box::new(move |p, s| tokio::spawn(f(p, s)))); + .push(Box::new(move |e, s| tokio::spawn(f(e, s)))); } } diff --git a/rustybot/src/pairs.rs b/rustybot/src/pairs.rs index a3e834c..506bf51 100644 --- a/rustybot/src/pairs.rs +++ b/rustybot/src/pairs.rs @@ -1,6 +1,8 @@ use std::collections::HashMap; -use crate::events::{EventDispatcher, Event}; +use bitfinex::positions::Position; + +use crate::events::{Event, EventDispatcher}; use crate::positions::PositionWrapper; use crate::strategy::Strategy; @@ -27,4 +29,31 @@ impl PairStatus { strategy, } } + + pub fn add_position(&mut self, position: Position) { + let (pw, events) = { + match &self.strategy { + Some(strategy) => { + strategy.position_on_new_tick(position) + }, + None => (PositionWrapper::new(position.clone(), position.pl(), position.pl_perc(), None), vec![]) + } + }; + + self.positions + .entry(self.current_tick) + .or_default() + .push(pw.clone()); + + // self.dispatcher.call_state_handlers(pw); + + for e in events { + self.add_event(e); + } + } + pub fn add_event(&mut self, event: Event) { + self.events.push(event); + + // self.dispatcher.call_handler(&event); + } } \ No newline at end of file diff --git a/rustybot/src/positions.rs b/rustybot/src/positions.rs index 9284030..b199ed8 100644 --- a/rustybot/src/positions.rs +++ b/rustybot/src/positions.rs @@ -24,11 +24,11 @@ pub struct PositionWrapper { position: Position, net_pl: f64, net_pl_perc: f64, - state: PositionState, + state: Option, } impl PositionWrapper { - pub fn new(position: Position, net_pl: f64, net_pl_perc: f64, state: PositionState) -> Self { + pub fn new(position: Position, net_pl: f64, net_pl_perc: f64, state: Option) -> Self { PositionWrapper { position, net_pl, -- 2.47.2 From deff46d143700bb80a9aeedbf93d9d0dc6701f8f Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sat, 2 Jan 2021 19:01:39 +0000 Subject: [PATCH 010/127] currency --- rustybot/src/bot.rs | 30 +++++++++++ rustybot/src/currency.rs | 111 +++++++++++++++++++++++++++++++++++++++ rustybot/src/main.rs | 2 + rustybot/src/ticker.rs | 2 +- 4 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 rustybot/src/bot.rs create mode 100644 rustybot/src/currency.rs diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs new file mode 100644 index 0000000..055cd9d --- /dev/null +++ b/rustybot/src/bot.rs @@ -0,0 +1,30 @@ +use std::collections::HashMap; + +use bitfinex::api::Bitfinex; + +use crate::pairs::PairStatus; +use crate::ticker::Ticker; +use core::time::Duration; + +struct BfxBot { + bfx: Bitfinex, + ticker: Ticker, + pair_status: HashMap, + quote: String, + account_info: String, + ledger: String, +} + +impl BfxBot { + pub fn new>(api_key: S, api_secret: S, pairs: Vec, quote: String, tick_duration: Duration) -> Self { + BfxBot { + bfx: Bitfinex::new(Some(api_key.into()), Some(api_secret.into())), + ticker: Ticker::new(tick_duration), + pair_status: HashMap::new(), + quote, + account_info: String::new(), + ledger: String::new(), + } + } +} + diff --git a/rustybot/src/currency.rs b/rustybot/src/currency.rs new file mode 100644 index 0000000..3d8e17e --- /dev/null +++ b/rustybot/src/currency.rs @@ -0,0 +1,111 @@ +#[derive(Clone)] +struct Symbol; + +impl Symbol { + const XMR: &'static str = "XMR"; + const BTC: &'static str = "BTC"; + const ETH: &'static str = "ETH"; + const USD: &'static str = "USD"; +} + +#[derive(Clone)] +struct TradingPair { + quote: Symbol +} + +impl TradingPair {} + +#[derive(Clone)] +struct Currency { + name: String, + amount: f64, + price: Option, +} + +impl Currency { + pub fn new, F: Into>(name: S, amount: F, price: Option) -> Self { + Currency { + name: name.into(), + amount: amount.into(), + price: price.map(|x| x.into()), + } + } + + pub fn name(&self) -> &str { + &self.name + } + pub fn amount(&self) -> f64 { + self.amount + } + pub fn price(&self) -> Option { + self.price + } +} + +#[derive(Clone)] +enum WalletKind { + Margin, + Exchange, + Funding, +} + +#[derive(Clone)] +struct Balance { + currency: Currency, + quote: Symbol, + quote_equivalent: f64, + wallet: WalletKind, +} + +impl Balance { + pub fn new(currency: Currency, quote: Symbol, quote_equivalent: f64, wallet: WalletKind) -> Self { + Balance { currency, quote, quote_equivalent, wallet } + } + + pub fn currency(&self) -> &Currency { + &self.currency + } + pub fn quote(&self) -> &Symbol { + &self.quote + } + pub fn quote_equivalent(&self) -> f64 { + self.quote_equivalent + } + pub fn wallet(&self) -> &WalletKind { + &self.wallet + } +} + +struct BalanceGroup { + quote: Symbol, + quote_equivalent: f64, + balances: Vec, +} + +impl BalanceGroup { + pub fn new(quote: Symbol) -> Self { + BalanceGroup { quote, balances: Vec::new(), quote_equivalent: 0f64 } + } + + pub fn add_balance(&mut self, balance: &Balance) { + self.balances.push(balance.clone()); + + self.quote_equivalent += balance.quote_equivalent() + } + + pub fn currency_names(&self) -> Vec { + self.balances.iter() + .map(|x| x.currency().name().into()) + .collect() + } + + pub fn quote(&self) -> &Symbol { + &self.quote + } + pub fn balances(&self) -> &Vec { + &self.balances + } +} + + + diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index 914da6e..d77b4e1 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -5,6 +5,8 @@ mod events; mod pairs; mod positions; mod strategy; +mod bot; +mod currency; pub type BoxError = Box; diff --git a/rustybot/src/ticker.rs b/rustybot/src/ticker.rs index 7bdf318..87226bb 100644 --- a/rustybot/src/ticker.rs +++ b/rustybot/src/ticker.rs @@ -7,7 +7,7 @@ pub struct Ticker { } impl Ticker { - fn new(duration: Duration) -> Self { + pub(crate) fn new(duration: Duration) -> Self { Ticker { duration, start_time: Instant::now(), -- 2.47.2 From 0470578739d72fc2fbe9b56963c5fb9df642e22a Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sun, 3 Jan 2021 15:54:36 +0000 Subject: [PATCH 011/127] currency structs --- rustybot/src/currency.rs | 106 +++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 49 deletions(-) diff --git a/rustybot/src/currency.rs b/rustybot/src/currency.rs index 3d8e17e..a9a67af 100644 --- a/rustybot/src/currency.rs +++ b/rustybot/src/currency.rs @@ -1,44 +1,52 @@ +use core::fmt; +use std::fmt::{Display, Formatter}; + +const XMR: Symbol = Symbol { name: "XMR" }; +const BTC: Symbol = Symbol { name: "BTC" }; +const ETH: Symbol = Symbol { name: "ETH" }; +const LTC: Symbol = Symbol { name: "LTC" }; +const USD: Symbol = Symbol { name: "USD" }; +const GBP: Symbol = Symbol { name: "GBP" }; +const EUR: Symbol = Symbol { name: "EUR" }; + + #[derive(Clone)] -struct Symbol; +struct Symbol { + name: &'static str +} impl Symbol { - const XMR: &'static str = "XMR"; - const BTC: &'static str = "BTC"; - const ETH: &'static str = "ETH"; - const USD: &'static str = "USD"; -} - -#[derive(Clone)] -struct TradingPair { - quote: Symbol -} - -impl TradingPair {} - -#[derive(Clone)] -struct Currency { - name: String, - amount: f64, - price: Option, -} - -impl Currency { - pub fn new, F: Into>(name: S, amount: F, price: Option) -> Self { - Currency { - name: name.into(), - amount: amount.into(), - price: price.map(|x| x.into()), - } - } - pub fn name(&self) -> &str { &self.name } - pub fn amount(&self) -> f64 { - self.amount +} + +impl Display for Symbol { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name) } - pub fn price(&self) -> Option { - self.price +} + +#[derive(Clone)] +struct SymbolPair { + quote: Symbol, + base: Symbol, +} + +impl SymbolPair { + fn trading_repr(&self) -> String { + format!("t{}{}", self.quote, self.base) + } + + fn funding_repr(&self) -> String { + format!("f{}{}", self.quote, self.base) + } + + pub fn quote(&self) -> &Symbol { + &self.quote + } + pub fn base(&self) -> &Symbol { + &self.base } } @@ -51,22 +59,26 @@ enum WalletKind { #[derive(Clone)] struct Balance { - currency: Currency, - quote: Symbol, + pair: SymbolPair, + base_price: f64, + base_amount: f64, quote_equivalent: f64, wallet: WalletKind, } impl Balance { - pub fn new(currency: Currency, quote: Symbol, quote_equivalent: f64, wallet: WalletKind) -> Self { - Balance { currency, quote, quote_equivalent, wallet } + pub fn new(pair: SymbolPair, base_price: f64, base_amount: f64, wallet: WalletKind) -> Self { + Balance { pair, base_price, base_amount, quote_equivalent: base_amount * base_price, wallet } } - pub fn currency(&self) -> &Currency { - &self.currency + pub fn pair(&self) -> &SymbolPair { + &self.pair } - pub fn quote(&self) -> &Symbol { - &self.quote + pub fn base_price(&self) -> f64 { + self.base_price + } + pub fn base_amount(&self) -> f64 { + self.base_amount } pub fn quote_equivalent(&self) -> f64 { self.quote_equivalent @@ -77,14 +89,13 @@ impl Balance { } struct BalanceGroup { - quote: Symbol, quote_equivalent: f64, balances: Vec, } impl BalanceGroup { - pub fn new(quote: Symbol) -> Self { - BalanceGroup { quote, balances: Vec::new(), quote_equivalent: 0f64 } + pub fn new() -> Self { + BalanceGroup { balances: Vec::new(), quote_equivalent: 0f64 } } pub fn add_balance(&mut self, balance: &Balance) { @@ -95,13 +106,10 @@ impl BalanceGroup { pub fn currency_names(&self) -> Vec { self.balances.iter() - .map(|x| x.currency().name().into()) + .map(|x| x.pair().base().name().into()) .collect() } - pub fn quote(&self) -> &Symbol { - &self.quote - } pub fn balances(&self) -> &Vec { &self.balances } -- 2.47.2 From f211b2cded245872d7e3f99f5271e2c7353d32e1 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 4 Jan 2021 10:45:54 +0000 Subject: [PATCH 012/127] other stuff --- rustybot/src/bot.rs | 77 +++++++++++++++++++++++++++++++--------- rustybot/src/currency.rs | 51 ++++++++++++++++---------- rustybot/src/main.rs | 23 +++++++++--- rustybot/src/pairs.rs | 7 ++-- rustybot/src/ticker.rs | 14 ++++++-- 5 files changed, 128 insertions(+), 44 deletions(-) diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index 055cd9d..be4a9e3 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -1,30 +1,73 @@ +use core::time::Duration; use std::collections::HashMap; use bitfinex::api::Bitfinex; +use bitfinex::positions::Position; +use tokio::time::delay_for; +use crate::BoxError; +use crate::currency::{Symbol, SymbolPair}; use crate::pairs::PairStatus; use crate::ticker::Ticker; -use core::time::Duration; +use bitfinex::ticker::TradingPairTicker; -struct BfxBot { - bfx: Bitfinex, - ticker: Ticker, - pair_status: HashMap, - quote: String, - account_info: String, - ledger: String, +pub struct BfxWrapper { + bfx: Bitfinex } -impl BfxBot { - pub fn new>(api_key: S, api_secret: S, pairs: Vec, quote: String, tick_duration: Duration) -> Self { - BfxBot { - bfx: Bitfinex::new(Some(api_key.into()), Some(api_secret.into())), - ticker: Ticker::new(tick_duration), - pair_status: HashMap::new(), - quote, - account_info: String::new(), - ledger: String::new(), +impl BfxWrapper { + pub fn new(api_key: &str, api_secret: &str) -> Self { + BfxWrapper { + bfx: Bitfinex::new(Some(api_key.into()), Some(api_secret.into())) } } + + pub async fn current_prices(&self, pair: &SymbolPair) -> Result { + let ticker: TradingPairTicker = self.bfx.ticker.trading_pair(pair.trading_repr()).await?; + + Ok(ticker) + } +} + +pub struct BfxBot<'a> { + pub bfx: BfxWrapper, + ticker: Ticker, + pair_status: Vec>, + quote: Symbol<'a>, + trading_symbols: Vec>, + // account_info: String, + // ledger: String, +} + +impl BfxBot { + pub fn new>(api_key: S, api_secret: S, trading_symbols: Vec, quote: Symbol, tick_duration: Duration) -> Self { + BfxBot { + bfx: BfxWrapper::new(&api_key.into(), &api_secret.into()), + ticker: Ticker::new(tick_duration), + pair_status: trading_symbols + .iter() + .map(|x| SymbolPair::new(quote.clone(), x.clone())) + .map(|x| PairStatus::new(x, 1, None)) + .collect(), + quote, + // account_info: String::new(), + // ledger: String::new(), + trading_symbols, + } + } + + pub async fn update(&mut self) { + println!("Updating..."); + delay_for(self.ticker.duration()).await; + self.ticker.inc(); + // self.update_pairs().await; + println!("Done!"); + } + + // async fn update_pairs(&mut self) { + // let active_positions = self.bfx.positions.active_positions().await?; + // + // for p in active_positions {} + // } } diff --git a/rustybot/src/currency.rs b/rustybot/src/currency.rs index a9a67af..d31dc05 100644 --- a/rustybot/src/currency.rs +++ b/rustybot/src/currency.rs @@ -1,21 +1,32 @@ use core::fmt; use std::fmt::{Display, Formatter}; -const XMR: Symbol = Symbol { name: "XMR" }; -const BTC: Symbol = Symbol { name: "BTC" }; -const ETH: Symbol = Symbol { name: "ETH" }; -const LTC: Symbol = Symbol { name: "LTC" }; -const USD: Symbol = Symbol { name: "USD" }; -const GBP: Symbol = Symbol { name: "GBP" }; -const EUR: Symbol = Symbol { name: "EUR" }; +pub const XMR: Symbol = Symbol { name: "XMR" }; +pub const BTC: Symbol = Symbol { name: "BTC" }; +pub const ETH: Symbol = Symbol { name: "ETH" }; +pub const LTC: Symbol = Symbol { name: "LTC" }; +pub const USD: Symbol = Symbol { name: "USD" }; +pub const GBP: Symbol = Symbol { name: "GBP" }; +pub const EUR: Symbol = Symbol { name: "EUR" }; #[derive(Clone)] -struct Symbol { - name: &'static str +pub struct Symbol<'a> { + name: &'a str } +impl From for Symbol where S:Into { + fn from(item: S) -> Self { + Symbol { name: &item.into() } + } +} + + impl Symbol { + pub fn new(name: &str) -> Self { + Symbol { name } + } + pub fn name(&self) -> &str { &self.name } @@ -28,17 +39,21 @@ impl Display for Symbol { } #[derive(Clone)] -struct SymbolPair { - quote: Symbol, - base: Symbol, +pub struct SymbolPair<'a> { + quote: Symbol<'a>, + base: Symbol<'a>, } impl SymbolPair { - fn trading_repr(&self) -> String { + pub fn new(quote: Symbol, base: Symbol) -> Self { + SymbolPair { quote, base } + } + + pub fn trading_repr(&self) -> String { format!("t{}{}", self.quote, self.base) } - fn funding_repr(&self) -> String { + pub fn funding_repr(&self) -> String { format!("f{}{}", self.quote, self.base) } @@ -58,8 +73,8 @@ enum WalletKind { } #[derive(Clone)] -struct Balance { - pair: SymbolPair, +struct Balance<'a> { + pair: SymbolPair<'a>, base_price: f64, base_amount: f64, quote_equivalent: f64, @@ -88,9 +103,9 @@ impl Balance { } } -struct BalanceGroup { +struct BalanceGroup<'a> { quote_equivalent: f64, - balances: Vec, + balances: Vec>, } impl BalanceGroup { diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index d77b4e1..bf3d1d9 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -1,4 +1,9 @@ use bitfinex::api::Bitfinex; +use bitfinex::ticker::TradingPairTicker; +use tokio::time::Duration; + +use crate::bot::BfxBot; +use crate::currency::{BTC, ETH, SymbolPair, USD, XMR}; mod ticker; mod events; @@ -13,11 +18,21 @@ pub type BoxError = Box; #[tokio::main] async fn main() -> Result<(), BoxError> { - let TEST_API_KEY="P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2"; - let TEST_API_SECRET="1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli"; + let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2"; + let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli"; - let bfx = Bitfinex::new(Some(TEST_API_KEY.into()), Some(TEST_API_SECRET.into())); + let mut bot = BfxBot::new(test_api_key, test_api_secret, vec![BTC, XMR, ETH], USD, Duration::new(20, 0)); + let btcusd = SymbolPair::new("USD".into(), "BTC".into()); + + loop { + bot.update().await; + + let ticker: TradingPairTicker = bot.bfx.current_prices(&btcusd).await?; + println!("{:?}", ticker); + if (2 < 1) { + break; + } + } - println!("{:?}", bfx.positions.active_positions().await); Ok(()) } diff --git a/rustybot/src/pairs.rs b/rustybot/src/pairs.rs index 506bf51..06d391a 100644 --- a/rustybot/src/pairs.rs +++ b/rustybot/src/pairs.rs @@ -5,9 +5,10 @@ use bitfinex::positions::Position; use crate::events::{Event, EventDispatcher}; use crate::positions::PositionWrapper; use crate::strategy::Strategy; +use crate::currency::SymbolPair; -pub struct PairStatus { - pair: String, +pub struct PairStatus<'a> { + pair: SymbolPair<'a>, dispatcher: EventDispatcher, prices: HashMap, events: Vec, @@ -18,7 +19,7 @@ pub struct PairStatus { } impl PairStatus { - pub fn new(pair: String, current_tick: u64, strategy: Option>) -> Self { + pub fn new(pair: SymbolPair, current_tick: u64, strategy: Option>) -> Self { PairStatus { pair, dispatcher: EventDispatcher::new(), diff --git a/rustybot/src/ticker.rs b/rustybot/src/ticker.rs index 87226bb..6a4d4c0 100644 --- a/rustybot/src/ticker.rs +++ b/rustybot/src/ticker.rs @@ -7,7 +7,7 @@ pub struct Ticker { } impl Ticker { - pub(crate) fn new(duration: Duration) -> Self { + pub fn new(duration: Duration) -> Self { Ticker { duration, start_time: Instant::now(), @@ -15,7 +15,17 @@ impl Ticker { } } - fn inc(&mut self) { + pub fn inc(&mut self) { self.current_tick += 1 } + + pub fn duration(&self) -> Duration { + self.duration + } + pub fn start_time(&self) -> Instant { + self.start_time + } + pub fn current_tick(&self) -> u64 { + self.current_tick + } } -- 2.47.2 From 168f324d6bdfefa2d823a1caa07b636353c2aa0f Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 4 Jan 2021 12:07:03 +0000 Subject: [PATCH 013/127] stuff --- rustybot/Cargo.toml | 2 +- rustybot/src/bot.rs | 20 +++++++++---- rustybot/src/currency.rs | 65 ++++++++++++++++++++++++---------------- rustybot/src/main.rs | 13 ++++---- rustybot/src/pairs.rs | 4 +-- 5 files changed, 62 insertions(+), 42 deletions(-) diff --git a/rustybot/Cargo.toml b/rustybot/Cargo.toml index 06577b5..8b3ea3c 100644 --- a/rustybot/Cargo.toml +++ b/rustybot/Cargo.toml @@ -10,4 +10,4 @@ edition = "2018" bitfinex = { path= "/home/giulio/dev/bitfinex-rs" } tokio = { version = "0.2", features=["full"]} tokio-tungstenite = "*" -futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] } \ No newline at end of file +futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] } diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index be4a9e3..3b2fa7b 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -23,18 +23,18 @@ impl BfxWrapper { } pub async fn current_prices(&self, pair: &SymbolPair) -> Result { - let ticker: TradingPairTicker = self.bfx.ticker.trading_pair(pair.trading_repr()).await?; + let ticker: TradingPairTicker = self.bfx.ticker.trading_pair(pair.clone()).await?; Ok(ticker) } } -pub struct BfxBot<'a> { +pub struct BfxBot { pub bfx: BfxWrapper, ticker: Ticker, - pair_status: Vec>, - quote: Symbol<'a>, - trading_symbols: Vec>, + pair_status: Vec, + quote: Symbol, + trading_symbols: Vec, // account_info: String, // ledger: String, } @@ -56,6 +56,16 @@ impl BfxBot { } } + pub async fn current_prices(&self, symbol: Symbol) -> Result { + let trading_pair = SymbolPair::new(self.quote.clone(), symbol.clone()); + + if !self.trading_symbols.contains(&symbol){ + return Err("Symbol not supported.".into()); + } + + self.bfx.current_prices(&trading_pair).await + } + pub async fn update(&mut self) { println!("Updating..."); delay_for(self.ticker.duration()).await; diff --git a/rustybot/src/currency.rs b/rustybot/src/currency.rs index d31dc05..c057b6e 100644 --- a/rustybot/src/currency.rs +++ b/rustybot/src/currency.rs @@ -1,30 +1,34 @@ use core::fmt; +use std::borrow::Cow; use std::fmt::{Display, Formatter}; -pub const XMR: Symbol = Symbol { name: "XMR" }; -pub const BTC: Symbol = Symbol { name: "BTC" }; -pub const ETH: Symbol = Symbol { name: "ETH" }; -pub const LTC: Symbol = Symbol { name: "LTC" }; -pub const USD: Symbol = Symbol { name: "USD" }; -pub const GBP: Symbol = Symbol { name: "GBP" }; -pub const EUR: Symbol = Symbol { name: "EUR" }; - - -#[derive(Clone)] -pub struct Symbol<'a> { - name: &'a str +#[derive(Clone, PartialEq, Hash)] +pub struct Symbol { + name: Cow<'static, str> } -impl From for Symbol where S:Into { +impl From for Symbol where S: Into { fn from(item: S) -> Self { - Symbol { name: &item.into() } + Symbol::new(item.into()) } } impl Symbol { - pub fn new(name: &str) -> Self { - Symbol { name } + pub const XMR: Symbol = Symbol::new_static("XMR"); + pub const BTC: Symbol = Symbol::new_static("BTC"); + pub const ETH: Symbol = Symbol::new_static("ETH"); + pub const LTC: Symbol = Symbol::new_static("LTC"); + pub const USD: Symbol = Symbol::new_static("USD"); + pub const GBP: Symbol = Symbol::new_static("GBP"); + pub const EUR: Symbol = Symbol::new_static("EUR"); + + pub fn new(name: String) -> Self { + Symbol { name: Cow::from(name) } + } + + pub const fn new_static(name: &'static str) -> Self { + Symbol { name: Cow::Borrowed(name) } } pub fn name(&self) -> &str { @@ -39,24 +43,21 @@ impl Display for Symbol { } #[derive(Clone)] -pub struct SymbolPair<'a> { - quote: Symbol<'a>, - base: Symbol<'a>, +pub struct SymbolPair { + quote: Symbol, + base: Symbol, } impl SymbolPair { pub fn new(quote: Symbol, base: Symbol) -> Self { SymbolPair { quote, base } } - pub fn trading_repr(&self) -> String { format!("t{}{}", self.quote, self.base) } - pub fn funding_repr(&self) -> String { format!("f{}{}", self.quote, self.base) } - pub fn quote(&self) -> &Symbol { &self.quote } @@ -65,6 +66,18 @@ impl SymbolPair { } } +impl Into for SymbolPair { + fn into(self) -> String { + format!("{}{}", self.base, self.quote) + } +} + +impl Display for SymbolPair { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}{}", self.base, self.quote) + } +} + #[derive(Clone)] enum WalletKind { Margin, @@ -73,8 +86,8 @@ enum WalletKind { } #[derive(Clone)] -struct Balance<'a> { - pair: SymbolPair<'a>, +struct Balance { + pair: SymbolPair, base_price: f64, base_amount: f64, quote_equivalent: f64, @@ -103,9 +116,9 @@ impl Balance { } } -struct BalanceGroup<'a> { +struct BalanceGroup { quote_equivalent: f64, - balances: Vec>, + balances: Vec, } impl BalanceGroup { diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index bf3d1d9..97cda11 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -1,9 +1,9 @@ use bitfinex::api::Bitfinex; use bitfinex::ticker::TradingPairTicker; -use tokio::time::Duration; +use tokio::time::{Duration, delay_for}; use crate::bot::BfxBot; -use crate::currency::{BTC, ETH, SymbolPair, USD, XMR}; +use crate::currency::{Symbol, SymbolPair}; mod ticker; mod events; @@ -21,17 +21,14 @@ async fn main() -> Result<(), BoxError> { let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2"; let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli"; - let mut bot = BfxBot::new(test_api_key, test_api_secret, vec![BTC, XMR, ETH], USD, Duration::new(20, 0)); - let btcusd = SymbolPair::new("USD".into(), "BTC".into()); + let mut bot = BfxBot::new(test_api_key, test_api_secret, vec![Symbol::BTC, Symbol::ETH, Symbol::XMR], Symbol::USD, Duration::new(20, 0)); loop { + let ticker = bot.current_prices("ETH".into()).await?; bot.update().await; - let ticker: TradingPairTicker = bot.bfx.current_prices(&btcusd).await?; + // let ticker = bot.current_prices("ETH".into()).await?; println!("{:?}", ticker); - if (2 < 1) { - break; - } } Ok(()) diff --git a/rustybot/src/pairs.rs b/rustybot/src/pairs.rs index 06d391a..cefd369 100644 --- a/rustybot/src/pairs.rs +++ b/rustybot/src/pairs.rs @@ -7,8 +7,8 @@ use crate::positions::PositionWrapper; use crate::strategy::Strategy; use crate::currency::SymbolPair; -pub struct PairStatus<'a> { - pair: SymbolPair<'a>, +pub struct PairStatus { + pair: SymbolPair, dispatcher: EventDispatcher, prices: HashMap, events: Vec, -- 2.47.2 From f56a3f84f8593983904634916635866a2e969a50 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 4 Jan 2021 12:28:40 +0000 Subject: [PATCH 014/127] dispatcher fixed --- rustybot/src/events.rs | 23 ++++++++++++----------- rustybot/src/pairs.rs | 18 ++++++++++-------- rustybot/src/strategy.rs | 3 ++- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 2a7de09..3d4ef6c 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -4,8 +4,8 @@ use std::future::Future; use bitfinex::positions::Position; use tokio::task::JoinHandle; -use crate::BoxError; use crate::pairs::PairStatus; +use crate::BoxError; #[derive(Copy, Clone)] pub enum SignalKind { @@ -66,7 +66,7 @@ impl Event { } pub struct EventDispatcher { - event_handlers: HashMap JoinHandle<()>>>>, + event_handlers: HashMap JoinHandle<()>>>>, } impl EventDispatcher { @@ -76,21 +76,22 @@ impl EventDispatcher { } } - pub fn call_handler(&mut self, event: &Event, status: &PairStatus) { - if let Some(callbacks) = self.event_handlers.get_mut(&event.kind()) { - for f in callbacks { - f(event.clone(), status); + pub fn call_event_handlers(&self, event: &Event, status: &PairStatus) { + if let Some(functions) = self.event_handlers.get(&event.kind()) { + for f in functions { + f(event, status); } } } - pub fn register_event_handler(&mut self, event: EventKind, mut f: F) - where - F: FnMut(Event, &PairStatus) -> Fut, - Fut: Future + Send, { + pub fn register_event_handler(&mut self, event: EventKind, f: F) + where + F: Fn(&Event, &PairStatus) -> Fut, + Fut: Future + Send, + { self.event_handlers .entry(event) .or_default() - .push(Box::new(move |e, s| tokio::spawn(f(e, s)))); + .push(Box::new(move |e, s| tokio::spawn(f(&e, s)))); } } diff --git a/rustybot/src/pairs.rs b/rustybot/src/pairs.rs index cefd369..41b579c 100644 --- a/rustybot/src/pairs.rs +++ b/rustybot/src/pairs.rs @@ -2,10 +2,10 @@ use std::collections::HashMap; use bitfinex::positions::Position; +use crate::currency::SymbolPair; use crate::events::{Event, EventDispatcher}; use crate::positions::PositionWrapper; use crate::strategy::Strategy; -use crate::currency::SymbolPair; pub struct PairStatus { pair: SymbolPair, @@ -34,17 +34,18 @@ impl PairStatus { pub fn add_position(&mut self, position: Position) { let (pw, events) = { match &self.strategy { - Some(strategy) => { - strategy.position_on_new_tick(position) - }, - None => (PositionWrapper::new(position.clone(), position.pl(), position.pl_perc(), None), vec![]) + Some(strategy) => strategy.position_on_new_tick(&position, &self), + None => ( + PositionWrapper::new(position.clone(), position.pl(), position.pl_perc(), None), + vec![], + ), } }; self.positions .entry(self.current_tick) .or_default() - .push(pw.clone()); + .push(pw); // self.dispatcher.call_state_handlers(pw); @@ -52,9 +53,10 @@ impl PairStatus { self.add_event(e); } } + pub fn add_event(&mut self, event: Event) { self.events.push(event); - // self.dispatcher.call_handler(&event); + self.dispatcher.call_event_handlers(&event, &self); } -} \ No newline at end of file +} diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index b8acf7d..8a5b482 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -2,7 +2,8 @@ use bitfinex::positions::Position; use crate::events::Event; use crate::positions::PositionWrapper; +use crate::pairs::PairStatus; pub trait Strategy { - fn position_on_new_tick(&self, position: Position) -> (PositionWrapper, Vec); + fn position_on_new_tick(&self, position: &Position, status: &PairStatus) -> (PositionWrapper, Vec); } \ No newline at end of file -- 2.47.2 From 0e33a09d8f3da4971130dd4e4ccb8a29b8b66995 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 4 Jan 2021 13:23:59 +0000 Subject: [PATCH 015/127] on any state on any state --- rustybot/src/events.rs | 46 ++++++++++++++++++++++++++++++++++++++- rustybot/src/pairs.rs | 6 +++-- rustybot/src/positions.rs | 35 ++++++++++++++++++++--------- 3 files changed, 74 insertions(+), 13 deletions(-) diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 3d4ef6c..ac96749 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -1,10 +1,12 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::future::Future; use bitfinex::positions::Position; +use tokio::stream::StreamExt; use tokio::task::JoinHandle; use crate::pairs::PairStatus; +use crate::positions::{PositionState, PositionWrapper}; use crate::BoxError; #[derive(Copy, Clone)] @@ -32,6 +34,7 @@ pub enum EventKind { TrailingStopMoved, OrderSubmitted, NewTick, + Any, } #[derive(Copy, Clone)] @@ -67,12 +70,21 @@ impl Event { pub struct EventDispatcher { event_handlers: HashMap JoinHandle<()>>>>, + position_state_handlers: + HashMap JoinHandle<()>>>>, + + on_any_event_handlers: Vec JoinHandle<()>>>, + on_any_position_state_handlers: + Vec JoinHandle<()>>>, } impl EventDispatcher { pub fn new() -> Self { EventDispatcher { event_handlers: HashMap::new(), + position_state_handlers: HashMap::new(), + on_any_event_handlers: Vec::new(), + on_any_position_state_handlers: Vec::new(), } } @@ -82,6 +94,24 @@ impl EventDispatcher { f(event, status); } } + + for f in &self.on_any_event_handlers { + f(event, status); + } + } + + pub fn call_position_state_handlers(&self, pw: &PositionWrapper, status: &PairStatus) { + if let Some(state) = pw.state() { + if let Some(functions) = self.position_state_handlers.get(&state) { + for f in functions { + f(pw, status); + } + } + } + + for f in &self.on_any_position_state_handlers { + f(pw, status); + } } pub fn register_event_handler(&mut self, event: EventKind, f: F) @@ -94,4 +124,18 @@ impl EventDispatcher { .or_default() .push(Box::new(move |e, s| tokio::spawn(f(&e, s)))); } + + pub fn register_positionstate_handler( + &mut self, + state: PositionState, + f: F, + ) where + F: Fn(&PositionWrapper, &PairStatus) -> Fut, + Fut: Future + Send, + { + self.position_state_handlers + .entry(state) + .or_default() + .push(Box::new(move |pw, s| tokio::spawn(f(&pw, s)))); + } } diff --git a/rustybot/src/pairs.rs b/rustybot/src/pairs.rs index 41b579c..fd507d3 100644 --- a/rustybot/src/pairs.rs +++ b/rustybot/src/pairs.rs @@ -45,10 +45,12 @@ impl PairStatus { self.positions .entry(self.current_tick) .or_default() - .push(pw); + .push(pw.clone()); - // self.dispatcher.call_state_handlers(pw); + // calling position state callbacks + self.dispatcher.call_position_state_handlers(&pw, &self); + // adding events and calling callbacks for e in events { self.add_event(e); } diff --git a/rustybot/src/positions.rs b/rustybot/src/positions.rs index b199ed8..8c896ff 100644 --- a/rustybot/src/positions.rs +++ b/rustybot/src/positions.rs @@ -1,6 +1,6 @@ use bitfinex::positions::Position; -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Eq, PartialEq, Hash)] pub enum PositionState { Critical, Loss, @@ -12,10 +12,11 @@ pub enum PositionState { impl PositionState { fn color(self) -> String { match self { - PositionState::Critical | PositionState::Loss => { "red" } - PositionState::BreakEven => { "yellow" } - PositionState::MinimumProfit | PositionState::Profit => { "green" } - }.into() + PositionState::Critical | PositionState::Loss => "red", + PositionState::BreakEven => "yellow", + PositionState::MinimumProfit | PositionState::Profit => "green", + } + .into() } } @@ -28,7 +29,12 @@ pub struct PositionWrapper { } impl PositionWrapper { - pub fn new(position: Position, net_pl: f64, net_pl_perc: f64, state: Option) -> Self { + pub fn new( + position: Position, + net_pl: f64, + net_pl_perc: f64, + state: Option, + ) -> Self { PositionWrapper { position, net_pl, @@ -36,8 +42,17 @@ impl PositionWrapper { state, } } + + pub fn position(&self) -> &Position { + &self.position + } + pub fn net_pl(&self) -> f64 { + self.net_pl + } + pub fn net_pl_perc(&self) -> f64 { + self.net_pl_perc + } + pub fn state(&self) -> Option { + self.state + } } - - - - -- 2.47.2 From 76e95f28594e73b4d5fde5589f941c6c905d1648 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 4 Jan 2021 13:27:30 +0000 Subject: [PATCH 016/127] fixed registers --- rustybot/src/events.rs | 24 ++++++++++++++++-------- rustybot/src/positions.rs | 1 + 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index ac96749..ad66e63 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -119,10 +119,12 @@ impl EventDispatcher { F: Fn(&Event, &PairStatus) -> Fut, Fut: Future + Send, { - self.event_handlers - .entry(event) - .or_default() - .push(Box::new(move |e, s| tokio::spawn(f(&e, s)))); + let f = Box::new(move |e, s| tokio::spawn(f(&e, s))); + + match event { + EventKind::Any => self.on_any_event_handlers.push(f), + _ => self.event_handlers.entry(event).or_default().push(f), + } } pub fn register_positionstate_handler( @@ -133,9 +135,15 @@ impl EventDispatcher { F: Fn(&PositionWrapper, &PairStatus) -> Fut, Fut: Future + Send, { - self.position_state_handlers - .entry(state) - .or_default() - .push(Box::new(move |pw, s| tokio::spawn(f(&pw, s)))); + let f = Box::new(move |pw, s| tokio::spawn(f(&pw, s))); + + match state { + PositionState::Any => self.on_any_position_state_handlers.push(f), + _ => self + .position_state_handlers + .entry(state) + .or_default() + .push(f), + } } } diff --git a/rustybot/src/positions.rs b/rustybot/src/positions.rs index 8c896ff..f588cb5 100644 --- a/rustybot/src/positions.rs +++ b/rustybot/src/positions.rs @@ -7,6 +7,7 @@ pub enum PositionState { BreakEven, MinimumProfit, Profit, + Any, } impl PositionState { -- 2.47.2 From d6cd0f1f2012e8817ac53a15871214b2a1a58446 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 4 Jan 2021 13:35:34 +0000 Subject: [PATCH 017/127] removed f variable, to fix --- rustybot/src/events.rs | 20 ++++++++++++-------- rustybot/src/positions.rs | 1 + 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index ad66e63..ad7f0ee 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -119,11 +119,15 @@ impl EventDispatcher { F: Fn(&Event, &PairStatus) -> Fut, Fut: Future + Send, { - let f = Box::new(move |e, s| tokio::spawn(f(&e, s))); - match event { - EventKind::Any => self.on_any_event_handlers.push(f), - _ => self.event_handlers.entry(event).or_default().push(f), + EventKind::Any => self + .on_any_event_handlers + .push(Box::new(move |e, s| tokio::spawn(f(&e, s)))), + _ => self + .event_handlers + .entry(event) + .or_default() + .push(Box::new(move |e, s| tokio::spawn(f(&e, s)))), } } @@ -135,15 +139,15 @@ impl EventDispatcher { F: Fn(&PositionWrapper, &PairStatus) -> Fut, Fut: Future + Send, { - let f = Box::new(move |pw, s| tokio::spawn(f(&pw, s))); - match state { - PositionState::Any => self.on_any_position_state_handlers.push(f), + PositionState::Any => self + .on_any_position_state_handlers + .push(Box::new(move |pw, s| tokio::spawn(f(&pw, s)))), _ => self .position_state_handlers .entry(state) .or_default() - .push(f), + .push(Box::new(move |pw, s| tokio::spawn(f(&pw, s)))), } } } diff --git a/rustybot/src/positions.rs b/rustybot/src/positions.rs index f588cb5..313f961 100644 --- a/rustybot/src/positions.rs +++ b/rustybot/src/positions.rs @@ -13,6 +13,7 @@ pub enum PositionState { impl PositionState { fn color(self) -> String { match self { + PositionState::Any => "blue", PositionState::Critical | PositionState::Loss => "red", PositionState::BreakEven => "yellow", PositionState::MinimumProfit | PositionState::Profit => "green", -- 2.47.2 From ea7c8394a3eec773b18014c6fe49e845dd8c75e8 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 4 Jan 2021 19:29:01 +0000 Subject: [PATCH 018/127] a lot of stuff --- rustybot/src/events.rs | 9 +++ rustybot/src/pairs.rs | 13 ++++- rustybot/src/strategy.rs | 119 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 136 insertions(+), 5 deletions(-) diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index ad7f0ee..e3c9ff2 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -21,6 +21,15 @@ pub struct EventMetadata { order_id: Option, } +impl EventMetadata { + pub fn new(position_id: Option, order_id: Option) -> Self { + EventMetadata { + position_id, + order_id, + } + } +} + #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum EventKind { NewMinimum, diff --git a/rustybot/src/pairs.rs b/rustybot/src/pairs.rs index fd507d3..e24e1a3 100644 --- a/rustybot/src/pairs.rs +++ b/rustybot/src/pairs.rs @@ -32,12 +32,13 @@ impl PairStatus { } pub fn add_position(&mut self, position: Position) { - let (pw, events) = { + let (pw, events, signals) = { match &self.strategy { Some(strategy) => strategy.position_on_new_tick(&position, &self), None => ( PositionWrapper::new(position.clone(), position.pl(), position.pl_perc(), None), vec![], + vec![], ), } }; @@ -61,4 +62,14 @@ impl PairStatus { self.dispatcher.call_event_handlers(&event, &self); } + + pub fn current_tick(&self) -> u64 { + self.current_tick + } + + pub fn previous_pw(&self, id: u64) -> Option<&PositionWrapper> { + self.positions + .get(&(self.current_tick - 1)) + .and_then(|x| x.iter().find(|x| x.position().position_id() == id)) + } } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 8a5b482..5b70ccd 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -1,9 +1,120 @@ +use std::collections::HashMap; + use bitfinex::positions::Position; -use crate::events::Event; -use crate::positions::PositionWrapper; +use crate::events::{Event, EventKind, EventMetadata, SignalKind}; use crate::pairs::PairStatus; +use crate::positions::{PositionState, PositionWrapper}; pub trait Strategy { - fn position_on_new_tick(&self, position: &Position, status: &PairStatus) -> (PositionWrapper, Vec); -} \ No newline at end of file + fn position_on_new_tick( + &self, + position: &Position, + status: &PairStatus, + ) -> (PositionWrapper, Vec, Vec); +} + +struct TrailingStop { + stop_percentages: HashMap, +} + +impl TrailingStop { + const BREAK_EVEN_PERC: f64 = 0.2; + const MIN_PROFIT_PERC: f64 = TrailingStop::BREAK_EVEN_PERC + 0.3; + const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 2.5; + const MAX_LOSS_PERC: f64 = -1.7; + + const TAKER_FEE: f64 = 0.2; + + pub fn new() -> Self { + TrailingStop { + stop_percentages: HashMap::new(), + } + } + + fn net_pl_percentage(pl: f64, fee: f64) -> f64 { + pl - fee + } +} + +impl Strategy for TrailingStop { + fn position_on_new_tick( + &self, + position: &Position, + status: &PairStatus, + ) -> (PositionWrapper, Vec, Vec) { + let mut signals = vec![]; + let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE); + let events = vec![]; + + let state = { + if pl_perc > TrailingStop::GOOD_PROFIT_PERC { + PositionState::Profit + } else if TrailingStop::MIN_PROFIT_PERC <= pl_perc + && pl_perc < TrailingStop::GOOD_PROFIT_PERC + { + PositionState::MinimumProfit + } else if 0.0 <= pl_perc && pl_perc < TrailingStop::MIN_PROFIT_PERC { + PositionState::BreakEven + } else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 { + PositionState::Loss + } else { + signals.push(SignalKind::ClosePosition); + PositionState::Critical + } + }; + + let opt_pre_pw = status.previous_pw(position.position_id()); + let event_metadata = EventMetadata::new(Some(position.position_id()), None); + let pw = PositionWrapper::new(position.clone(), position.pl(), pl_perc, Some(state)); + + match opt_pre_pw { + Some(prev) => { + if prev.state() == Some(state) { + return (pw, events, signals); + } + } + None => return (pw, events, signals), + }; + + let events = { + let mut events = vec![]; + + if state == PositionState::Profit { + events.push(Event::new( + EventKind::ReachedGoodProfit, + status.current_tick(), + Some(event_metadata), + )); + } else if state == PositionState::MinimumProfit { + events.push(Event::new( + EventKind::ReachedMinProfit, + status.current_tick(), + Some(event_metadata), + )); + } else if state == PositionState::BreakEven { + events.push(Event::new( + EventKind::ReachedBreakEven, + status.current_tick(), + Some(event_metadata), + )); + } else if state == PositionState::Loss { + events.push(Event::new( + EventKind::ReachedLoss, + status.current_tick(), + Some(event_metadata), + )); + } else { + events.push(Event::new( + EventKind::ReachedMaxLoss, + status.current_tick(), + Some(event_metadata), + )); + } + + events + }; + + return (pw, events, signals); + } +} -- 2.47.2 From 78b57b3899274ab64fd351ee98ce6ad6adc28c1b Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Tue, 5 Jan 2021 12:58:47 +0000 Subject: [PATCH 019/127] traits and shit --- rustybot/Cargo.lock | 12 ++++ rustybot/Cargo.toml | 1 + rustybot/src/bot.rs | 43 +++++--------- rustybot/src/events.rs | 26 ++++----- rustybot/src/main.rs | 43 ++++++++------ rustybot/src/orders.rs | 21 +++++++ rustybot/src/pairs.rs | 17 +++--- rustybot/src/positions.rs | 118 ++++++++++++++++++++++++++------------ rustybot/src/strategy.rs | 13 ++--- 9 files changed, 179 insertions(+), 115 deletions(-) create mode 100644 rustybot/src/orders.rs diff --git a/rustybot/Cargo.lock b/rustybot/Cargo.lock index a320dd2..90fdb3c 100644 --- a/rustybot/Cargo.lock +++ b/rustybot/Cargo.lock @@ -15,6 +15,17 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" +[[package]] +name = "async-trait" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "1.0.1" @@ -1016,6 +1027,7 @@ checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" name = "rustybot" version = "0.1.0" dependencies = [ + "async-trait", "bitfinex", "futures-util", "tokio 0.2.24", diff --git a/rustybot/Cargo.toml b/rustybot/Cargo.toml index 8b3ea3c..fd2d334 100644 --- a/rustybot/Cargo.toml +++ b/rustybot/Cargo.toml @@ -11,3 +11,4 @@ bitfinex = { path= "/home/giulio/dev/bitfinex-rs" } tokio = { version = "0.2", features=["full"]} tokio-tungstenite = "*" futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] } +async-trait = "0.1" \ No newline at end of file diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index 3b2fa7b..1be1429 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -3,34 +3,17 @@ use std::collections::HashMap; use bitfinex::api::Bitfinex; use bitfinex::positions::Position; +use bitfinex::ticker::TradingPairTicker; use tokio::time::delay_for; -use crate::BoxError; +use crate::connectors::Connector; use crate::currency::{Symbol, SymbolPair}; use crate::pairs::PairStatus; use crate::ticker::Ticker; -use bitfinex::ticker::TradingPairTicker; +use crate::BoxError; -pub struct BfxWrapper { - bfx: Bitfinex -} - -impl BfxWrapper { - pub fn new(api_key: &str, api_secret: &str) -> Self { - BfxWrapper { - bfx: Bitfinex::new(Some(api_key.into()), Some(api_secret.into())) - } - } - - pub async fn current_prices(&self, pair: &SymbolPair) -> Result { - let ticker: TradingPairTicker = self.bfx.ticker.trading_pair(pair.clone()).await?; - - Ok(ticker) - } -} - -pub struct BfxBot { - pub bfx: BfxWrapper, +pub struct BfxBot<'a> { + connector: Box, ticker: Ticker, pair_status: Vec, quote: Symbol, @@ -39,10 +22,15 @@ pub struct BfxBot { // ledger: String, } -impl BfxBot { - pub fn new>(api_key: S, api_secret: S, trading_symbols: Vec, quote: Symbol, tick_duration: Duration) -> Self { +impl<'a> BfxBot<'a> { + pub fn new( + connector: C, + trading_symbols: Vec, + quote: Symbol, + tick_duration: Duration, + ) -> Self { BfxBot { - bfx: BfxWrapper::new(&api_key.into(), &api_secret.into()), + connector: Box::new(connector), ticker: Ticker::new(tick_duration), pair_status: trading_symbols .iter() @@ -59,11 +47,11 @@ impl BfxBot { pub async fn current_prices(&self, symbol: Symbol) -> Result { let trading_pair = SymbolPair::new(self.quote.clone(), symbol.clone()); - if !self.trading_symbols.contains(&symbol){ + if !self.trading_symbols.contains(&symbol) { return Err("Symbol not supported.".into()); } - self.bfx.current_prices(&trading_pair).await + self.connector.current_prices(&trading_pair).await } pub async fn update(&mut self) { @@ -80,4 +68,3 @@ impl BfxBot { // for p in active_positions {} // } } - diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index e3c9ff2..5ef52e5 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -1,12 +1,11 @@ use std::collections::{HashMap, HashSet}; use std::future::Future; -use bitfinex::positions::Position; use tokio::stream::StreamExt; use tokio::task::JoinHandle; use crate::pairs::PairStatus; -use crate::positions::{PositionState, PositionWrapper}; +use crate::positions::{Position, PositionState}; use crate::BoxError; #[derive(Copy, Clone)] @@ -80,11 +79,10 @@ impl Event { pub struct EventDispatcher { event_handlers: HashMap JoinHandle<()>>>>, position_state_handlers: - HashMap JoinHandle<()>>>>, + HashMap JoinHandle<()>>>>, on_any_event_handlers: Vec JoinHandle<()>>>, - on_any_position_state_handlers: - Vec JoinHandle<()>>>, + on_any_position_state_handlers: Vec JoinHandle<()>>>, } impl EventDispatcher { @@ -109,17 +107,15 @@ impl EventDispatcher { } } - pub fn call_position_state_handlers(&self, pw: &PositionWrapper, status: &PairStatus) { - if let Some(state) = pw.state() { - if let Some(functions) = self.position_state_handlers.get(&state) { - for f in functions { - f(pw, status); - } + pub fn call_position_state_handlers(&self, position: &Position, status: &PairStatus) { + if let Some(functions) = self.position_state_handlers.get(&position.state()) { + for f in functions { + f(position, status); } } for f in &self.on_any_position_state_handlers { - f(pw, status); + f(position, status); } } @@ -145,18 +141,18 @@ impl EventDispatcher { state: PositionState, f: F, ) where - F: Fn(&PositionWrapper, &PairStatus) -> Fut, + F: Fn(&Position, &PairStatus) -> Fut, Fut: Future + Send, { match state { PositionState::Any => self .on_any_position_state_handlers - .push(Box::new(move |pw, s| tokio::spawn(f(&pw, s)))), + .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))), _ => self .position_state_handlers .entry(state) .or_default() - .push(Box::new(move |pw, s| tokio::spawn(f(&pw, s)))), + .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))), } } } diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index 97cda11..5885bdd 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -1,35 +1,40 @@ -use bitfinex::api::Bitfinex; -use bitfinex::ticker::TradingPairTicker; -use tokio::time::{Duration, delay_for}; +use tokio::time::{delay_for, Duration}; use crate::bot::BfxBot; use crate::currency::{Symbol, SymbolPair}; -mod ticker; +mod bot; +mod connectors; +mod currency; mod events; +mod orders; mod pairs; mod positions; mod strategy; -mod bot; -mod currency; - +mod ticker; pub type BoxError = Box; #[tokio::main] async fn main() -> Result<(), BoxError> { - let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2"; - let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli"; - - let mut bot = BfxBot::new(test_api_key, test_api_secret, vec![Symbol::BTC, Symbol::ETH, Symbol::XMR], Symbol::USD, Duration::new(20, 0)); - - loop { - let ticker = bot.current_prices("ETH".into()).await?; - bot.update().await; - - // let ticker = bot.current_prices("ETH".into()).await?; - println!("{:?}", ticker); - } + // let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2"; + // let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli"; + // + // let mut bot = BfxBot::new( + // test_api_key, + // test_api_secret, + // vec![Symbol::BTC, Symbol::ETH, Symbol::XMR], + // Symbol::USD, + // Duration::new(20, 0), + // ); + // + // loop { + // let ticker = bot.current_prices("ETH".into()).await?; + // bot.update().await; + // + // // let ticker = bot.current_prices("ETH".into()).await?; + // println!("{:?}", ticker); + // } Ok(()) } diff --git a/rustybot/src/orders.rs b/rustybot/src/orders.rs new file mode 100644 index 0000000..7569fa8 --- /dev/null +++ b/rustybot/src/orders.rs @@ -0,0 +1,21 @@ +pub struct Order { + pub id: i64, + pub group_id: Option, + pub client_id: i64, + pub symbol: String, + pub creation_timestamp: i64, + pub update_timestamp: i64, + pub amount: f64, + pub amount_original: f64, + pub order_type: String, + pub previous_order_type: Option, + pub flags: Option, + pub order_status: Option, + pub price: f64, + pub price_avg: f64, + pub price_trailing: Option, + pub price_aux_limit: Option, + pub notify: i32, + pub hidden: i32, + pub placed_id: Option, +} diff --git a/rustybot/src/pairs.rs b/rustybot/src/pairs.rs index e24e1a3..b2e986b 100644 --- a/rustybot/src/pairs.rs +++ b/rustybot/src/pairs.rs @@ -1,10 +1,9 @@ use std::collections::HashMap; -use bitfinex::positions::Position; - use crate::currency::SymbolPair; use crate::events::{Event, EventDispatcher}; -use crate::positions::PositionWrapper; +use crate::orders::Order; +use crate::positions::Position; use crate::strategy::Strategy; pub struct PairStatus { @@ -12,8 +11,8 @@ pub struct PairStatus { dispatcher: EventDispatcher, prices: HashMap, events: Vec, - // orders: HashMap>, - positions: HashMap>, + orders: HashMap>, + positions: HashMap>, current_tick: u64, strategy: Option>, } @@ -28,6 +27,7 @@ impl PairStatus { positions: HashMap::new(), current_tick, strategy, + orders: HashMap::new(), } } @@ -36,7 +36,8 @@ impl PairStatus { match &self.strategy { Some(strategy) => strategy.position_on_new_tick(&position, &self), None => ( - PositionWrapper::new(position.clone(), position.pl(), position.pl_perc(), None), + position, + // PositionWrapper::new(position.clone(), position.pl(), position.pl_perc(), None), vec![], vec![], ), @@ -67,9 +68,9 @@ impl PairStatus { self.current_tick } - pub fn previous_pw(&self, id: u64) -> Option<&PositionWrapper> { + pub fn previous_pw(&self, id: u64) -> Option<&Position> { self.positions .get(&(self.current_tick - 1)) - .and_then(|x| x.iter().find(|x| x.position().position_id() == id)) + .and_then(|x| x.iter().find(|x| x.position_id() == id)) } } diff --git a/rustybot/src/positions.rs b/rustybot/src/positions.rs index 313f961..8d3655e 100644 --- a/rustybot/src/positions.rs +++ b/rustybot/src/positions.rs @@ -1,4 +1,81 @@ -use bitfinex::positions::Position; +use crate::currency::{Symbol, SymbolPair}; + +#[derive(Clone)] +pub struct Position { + pair: SymbolPair, + state: PositionState, + amount: f64, + base_price: f64, + pl: f64, + pl_perc: f64, + price_liq: f64, + position_id: u64, + creation_date: Option, + creation_update: Option, +} + +impl Position { + pub fn new( + pair: SymbolPair, + state: PositionState, + amount: f64, + base_price: f64, + pl: f64, + pl_perc: f64, + price_liq: f64, + position_id: u64, + ) -> Self { + Position { + pair, + state, + amount, + base_price, + pl, + pl_perc, + price_liq, + position_id, + creation_date: None, + creation_update: None, + } + } + + pub fn with_creation_date(mut self, creation_date: u64) -> Self { + self.creation_date = Some(creation_date); + self.creation_update = Some(creation_date); + self + } + + pub fn pair(&self) -> &SymbolPair { + &self.pair + } + pub fn state(&self) -> PositionState { + self.state + } + pub fn amount(&self) -> f64 { + self.amount + } + pub fn base_price(&self) -> f64 { + self.base_price + } + pub fn pl(&self) -> f64 { + self.pl + } + pub fn pl_perc(&self) -> f64 { + self.pl_perc + } + pub fn price_liq(&self) -> f64 { + self.price_liq + } + pub fn position_id(&self) -> u64 { + self.position_id + } + pub fn creation_date(&self) -> Option { + self.creation_date + } + pub fn creation_update(&self) -> Option { + self.creation_update + } +} #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub enum PositionState { @@ -7,6 +84,7 @@ pub enum PositionState { BreakEven, MinimumProfit, Profit, + Closed, Any, } @@ -17,44 +95,8 @@ impl PositionState { PositionState::Critical | PositionState::Loss => "red", PositionState::BreakEven => "yellow", PositionState::MinimumProfit | PositionState::Profit => "green", + PositionState::Closed => "gray", } .into() } } - -#[derive(Clone)] -pub struct PositionWrapper { - position: Position, - net_pl: f64, - net_pl_perc: f64, - state: Option, -} - -impl PositionWrapper { - pub fn new( - position: Position, - net_pl: f64, - net_pl_perc: f64, - state: Option, - ) -> Self { - PositionWrapper { - position, - net_pl, - net_pl_perc, - state, - } - } - - pub fn position(&self) -> &Position { - &self.position - } - pub fn net_pl(&self) -> f64 { - self.net_pl - } - pub fn net_pl_perc(&self) -> f64 { - self.net_pl_perc - } - pub fn state(&self) -> Option { - self.state - } -} diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 5b70ccd..7251a98 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -1,17 +1,15 @@ use std::collections::HashMap; -use bitfinex::positions::Position; - use crate::events::{Event, EventKind, EventMetadata, SignalKind}; use crate::pairs::PairStatus; -use crate::positions::{PositionState, PositionWrapper}; +use crate::positions::{Position, PositionState}; pub trait Strategy { fn position_on_new_tick( &self, position: &Position, status: &PairStatus, - ) -> (PositionWrapper, Vec, Vec); + ) -> (Position, Vec, Vec); } struct TrailingStop { @@ -42,7 +40,7 @@ impl Strategy for TrailingStop { &self, position: &Position, status: &PairStatus, - ) -> (PositionWrapper, Vec, Vec) { + ) -> (Position, Vec, Vec) { let mut signals = vec![]; let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE); let events = vec![]; @@ -66,11 +64,12 @@ impl Strategy for TrailingStop { let opt_pre_pw = status.previous_pw(position.position_id()); let event_metadata = EventMetadata::new(Some(position.position_id()), None); - let pw = PositionWrapper::new(position.clone(), position.pl(), pl_perc, Some(state)); + // let pw = PositionWrapper::new(position.clone(), position.pl(), pl_perc, Some(state)); + let pw = position.clone(); match opt_pre_pw { Some(prev) => { - if prev.state() == Some(state) { + if prev.state() == state { return (pw, events, signals); } } -- 2.47.2 From ae648b70a402920b5f33fb949038ffdf7b94d4c7 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Tue, 5 Jan 2021 14:51:03 +0000 Subject: [PATCH 020/127] other traits and shit --- rustybot/Cargo.lock | 37 ++++++++++++++++++++++++ rustybot/Cargo.toml | 3 +- rustybot/src/currency.rs | 59 +++++++++++++++++++++++++++++++-------- rustybot/src/events.rs | 32 +++++++++++---------- rustybot/src/positions.rs | 40 ++++++++++++++++++-------- rustybot/src/strategy.rs | 22 +++++++-------- 6 files changed, 142 insertions(+), 51 deletions(-) diff --git a/rustybot/Cargo.lock b/rustybot/Cargo.lock index 90fdb3c..1acf9c5 100644 --- a/rustybot/Cargo.lock +++ b/rustybot/Cargo.lock @@ -15,6 +15,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" +[[package]] +name = "aho-corasick" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +dependencies = [ + "memchr", +] + [[package]] name = "async-trait" version = "0.1.42" @@ -958,6 +967,24 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "regex" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -1030,6 +1057,7 @@ dependencies = [ "async-trait", "bitfinex", "futures-util", + "regex", "tokio 0.2.24", "tokio-tungstenite", ] @@ -1195,6 +1223,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + [[package]] name = "tinyvec" version = "1.1.0" diff --git a/rustybot/Cargo.toml b/rustybot/Cargo.toml index fd2d334..4f37fe1 100644 --- a/rustybot/Cargo.toml +++ b/rustybot/Cargo.toml @@ -11,4 +11,5 @@ bitfinex = { path= "/home/giulio/dev/bitfinex-rs" } tokio = { version = "0.2", features=["full"]} tokio-tungstenite = "*" futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] } -async-trait = "0.1" \ No newline at end of file +async-trait = "0.1" +regex = "1" \ No newline at end of file diff --git a/rustybot/src/currency.rs b/rustybot/src/currency.rs index c057b6e..97a8594 100644 --- a/rustybot/src/currency.rs +++ b/rustybot/src/currency.rs @@ -1,19 +1,26 @@ use core::fmt; use std::borrow::Cow; -use std::fmt::{Display, Formatter}; +use std::convert::TryFrom; +use std::fmt::{Display, Error, Formatter}; + +use regex::Regex; + +use crate::BoxError; #[derive(Clone, PartialEq, Hash)] pub struct Symbol { - name: Cow<'static, str> + name: Cow<'static, str>, } -impl From for Symbol where S: Into { +impl From for Symbol +where + S: Into, +{ fn from(item: S) -> Self { Symbol::new(item.into()) } } - impl Symbol { pub const XMR: Symbol = Symbol::new_static("XMR"); pub const BTC: Symbol = Symbol::new_static("BTC"); @@ -24,11 +31,15 @@ impl Symbol { pub const EUR: Symbol = Symbol::new_static("EUR"); pub fn new(name: String) -> Self { - Symbol { name: Cow::from(name) } + Symbol { + name: Cow::from(name), + } } pub const fn new_static(name: &'static str) -> Self { - Symbol { name: Cow::Borrowed(name) } + Symbol { + name: Cow::Borrowed(name), + } } pub fn name(&self) -> &str { @@ -72,6 +83,23 @@ impl Into for SymbolPair { } } +impl TryFrom<&str> for SymbolPair { + type Error = BoxError; + + fn try_from(value: &str) -> Result { + const REGEX: &str = r"^[t|f](?P\w{3,4}):?(?P\w{3,4})"; + + let captures = Regex::new(REGEX)?.captures(&value).ok_or("Invalid input")?; + let quote = captures.name("quote").ok_or("Quote not found")?.as_str(); + let base = captures.name("base").ok_or("Base not found")?.as_str(); + + Ok(SymbolPair { + quote: quote.into(), + base: base.into(), + }) + } +} + impl Display for SymbolPair { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{}{}", self.base, self.quote) @@ -96,7 +124,13 @@ struct Balance { impl Balance { pub fn new(pair: SymbolPair, base_price: f64, base_amount: f64, wallet: WalletKind) -> Self { - Balance { pair, base_price, base_amount, quote_equivalent: base_amount * base_price, wallet } + Balance { + pair, + base_price, + base_amount, + quote_equivalent: base_amount * base_price, + wallet, + } } pub fn pair(&self) -> &SymbolPair { @@ -123,7 +157,10 @@ struct BalanceGroup { impl BalanceGroup { pub fn new() -> Self { - BalanceGroup { balances: Vec::new(), quote_equivalent: 0f64 } + BalanceGroup { + balances: Vec::new(), + quote_equivalent: 0f64, + } } pub fn add_balance(&mut self, balance: &Balance) { @@ -133,7 +170,8 @@ impl BalanceGroup { } pub fn currency_names(&self) -> Vec { - self.balances.iter() + self.balances + .iter() .map(|x| x.pair().base().name().into()) .collect() } @@ -142,6 +180,3 @@ impl BalanceGroup { &self.balances } } - - - diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 5ef52e5..9cb3bd0 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -5,7 +5,7 @@ use tokio::stream::StreamExt; use tokio::task::JoinHandle; use crate::pairs::PairStatus; -use crate::positions::{Position, PositionState}; +use crate::positions::{Position, PositionProfitState, PositionState}; use crate::BoxError; #[derive(Copy, Clone)] @@ -78,20 +78,20 @@ impl Event { pub struct EventDispatcher { event_handlers: HashMap JoinHandle<()>>>>, - position_state_handlers: - HashMap JoinHandle<()>>>>, + profit_state_handlers: + HashMap JoinHandle<()>>>>, on_any_event_handlers: Vec JoinHandle<()>>>, - on_any_position_state_handlers: Vec JoinHandle<()>>>, + on_any_profit_state_handlers: Vec JoinHandle<()>>>, } impl EventDispatcher { pub fn new() -> Self { EventDispatcher { event_handlers: HashMap::new(), - position_state_handlers: HashMap::new(), + profit_state_handlers: HashMap::new(), on_any_event_handlers: Vec::new(), - on_any_position_state_handlers: Vec::new(), + on_any_profit_state_handlers: Vec::new(), } } @@ -108,13 +108,15 @@ impl EventDispatcher { } pub fn call_position_state_handlers(&self, position: &Position, status: &PairStatus) { - if let Some(functions) = self.position_state_handlers.get(&position.state()) { - for f in functions { - f(position, status); + if let Some(profit_state) = &position.profit_state() { + if let Some(functions) = self.profit_state_handlers.get(profit_state) { + for f in functions { + f(position, status); + } } } - for f in &self.on_any_position_state_handlers { + for f in &self.on_any_profit_state_handlers { f(position, status); } } @@ -138,18 +140,18 @@ impl EventDispatcher { pub fn register_positionstate_handler( &mut self, - state: PositionState, + state: PositionProfitState, f: F, ) where F: Fn(&Position, &PairStatus) -> Fut, Fut: Future + Send, { match state { - PositionState::Any => self - .on_any_position_state_handlers - .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))), + // PositionProfitState::Any => self + // .on_any_position_state_handlers + // .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))), _ => self - .position_state_handlers + .profit_state_handlers .entry(state) .or_default() .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))), diff --git a/rustybot/src/positions.rs b/rustybot/src/positions.rs index 8d3655e..444ad94 100644 --- a/rustybot/src/positions.rs +++ b/rustybot/src/positions.rs @@ -4,6 +4,7 @@ use crate::currency::{Symbol, SymbolPair}; pub struct Position { pair: SymbolPair, state: PositionState, + profit_state: Option, amount: f64, base_price: f64, pl: f64, @@ -36,12 +37,22 @@ impl Position { position_id, creation_date: None, creation_update: None, + profit_state: None, } } - pub fn with_creation_date(mut self, creation_date: u64) -> Self { - self.creation_date = Some(creation_date); - self.creation_update = Some(creation_date); + pub fn with_creation_date(mut self, creation_date: Option) -> Self { + self.creation_date = creation_date; + self + } + + pub fn with_creation_update(mut self, creation_update: Option) -> Self { + self.creation_update = creation_update; + self + } + + pub fn with_profit_state(mut self, profit_state: Option) -> Self { + self.profit_state = profit_state; self } @@ -69,6 +80,9 @@ impl Position { pub fn position_id(&self) -> u64 { self.position_id } + pub fn profit_state(&self) -> Option { + self.profit_state + } pub fn creation_date(&self) -> Option { self.creation_date } @@ -78,25 +92,27 @@ impl Position { } #[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub enum PositionState { +pub enum PositionProfitState { Critical, Loss, BreakEven, MinimumProfit, Profit, - Closed, - Any, } -impl PositionState { +impl PositionProfitState { fn color(self) -> String { match self { - PositionState::Any => "blue", - PositionState::Critical | PositionState::Loss => "red", - PositionState::BreakEven => "yellow", - PositionState::MinimumProfit | PositionState::Profit => "green", - PositionState::Closed => "gray", + PositionProfitState::Critical | PositionProfitState::Loss => "red", + PositionProfitState::BreakEven => "yellow", + PositionProfitState::MinimumProfit | PositionProfitState::Profit => "green", } .into() } } + +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub enum PositionState { + Closed, + Open, +} diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 7251a98..24f3fdb 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use crate::events::{Event, EventKind, EventMetadata, SignalKind}; use crate::pairs::PairStatus; -use crate::positions::{Position, PositionState}; +use crate::positions::{Position, PositionProfitState, PositionState}; pub trait Strategy { fn position_on_new_tick( @@ -47,18 +47,18 @@ impl Strategy for TrailingStop { let state = { if pl_perc > TrailingStop::GOOD_PROFIT_PERC { - PositionState::Profit + PositionProfitState::Profit } else if TrailingStop::MIN_PROFIT_PERC <= pl_perc && pl_perc < TrailingStop::GOOD_PROFIT_PERC { - PositionState::MinimumProfit + PositionProfitState::MinimumProfit } else if 0.0 <= pl_perc && pl_perc < TrailingStop::MIN_PROFIT_PERC { - PositionState::BreakEven + PositionProfitState::BreakEven } else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 { - PositionState::Loss + PositionProfitState::Loss } else { signals.push(SignalKind::ClosePosition); - PositionState::Critical + PositionProfitState::Critical } }; @@ -69,7 +69,7 @@ impl Strategy for TrailingStop { match opt_pre_pw { Some(prev) => { - if prev.state() == state { + if prev.profit_state() == Some(state) { return (pw, events, signals); } } @@ -79,25 +79,25 @@ impl Strategy for TrailingStop { let events = { let mut events = vec![]; - if state == PositionState::Profit { + if state == PositionProfitState::Profit { events.push(Event::new( EventKind::ReachedGoodProfit, status.current_tick(), Some(event_metadata), )); - } else if state == PositionState::MinimumProfit { + } else if state == PositionProfitState::MinimumProfit { events.push(Event::new( EventKind::ReachedMinProfit, status.current_tick(), Some(event_metadata), )); - } else if state == PositionState::BreakEven { + } else if state == PositionProfitState::BreakEven { events.push(Event::new( EventKind::ReachedBreakEven, status.current_tick(), Some(event_metadata), )); - } else if state == PositionState::Loss { + } else if state == PositionProfitState::Loss { events.push(Event::new( EventKind::ReachedLoss, status.current_tick(), -- 2.47.2 From 0e2673837cc3ec260f167d442fdc5dc4e11d540d Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Tue, 5 Jan 2021 19:50:14 +0000 Subject: [PATCH 021/127] positions with strategy working --- rustybot/Cargo.lock | 7 +++++ rustybot/Cargo.toml | 3 +- rustybot/src/bot.rs | 57 ++++++++++++++++++++++++---------- rustybot/src/currency.rs | 12 +++++--- rustybot/src/events.rs | 8 ++--- rustybot/src/main.rs | 31 ++++++++----------- rustybot/src/pairs.rs | 65 +++++++++++++++++++++++++++++---------- rustybot/src/positions.rs | 6 ++-- rustybot/src/strategy.rs | 17 +++++----- 9 files changed, 133 insertions(+), 73 deletions(-) diff --git a/rustybot/Cargo.lock b/rustybot/Cargo.lock index 1acf9c5..c51ed09 100644 --- a/rustybot/Cargo.lock +++ b/rustybot/Cargo.lock @@ -223,6 +223,12 @@ dependencies = [ "generic-array 0.14.4", ] +[[package]] +name = "dyn-clone" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" + [[package]] name = "encoding_rs" version = "0.8.26" @@ -1056,6 +1062,7 @@ version = "0.1.0" dependencies = [ "async-trait", "bitfinex", + "dyn-clone", "futures-util", "regex", "tokio 0.2.24", diff --git a/rustybot/Cargo.toml b/rustybot/Cargo.toml index 4f37fe1..3985630 100644 --- a/rustybot/Cargo.toml +++ b/rustybot/Cargo.toml @@ -12,4 +12,5 @@ tokio = { version = "0.2", features=["full"]} tokio-tungstenite = "*" futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] } async-trait = "0.1" -regex = "1" \ No newline at end of file +regex = "1" +dyn-clone = "1" \ No newline at end of file diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index 1be1429..d7bba5a 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -9,17 +9,16 @@ use tokio::time::delay_for; use crate::connectors::Connector; use crate::currency::{Symbol, SymbolPair}; use crate::pairs::PairStatus; +use crate::strategy::Strategy; use crate::ticker::Ticker; use crate::BoxError; pub struct BfxBot<'a> { connector: Box, ticker: Ticker, - pair_status: Vec, + pair_statuses: Vec>, quote: Symbol, trading_symbols: Vec, - // account_info: String, - // ledger: String, } impl<'a> BfxBot<'a> { @@ -32,18 +31,24 @@ impl<'a> BfxBot<'a> { BfxBot { connector: Box::new(connector), ticker: Ticker::new(tick_duration), - pair_status: trading_symbols + pair_statuses: trading_symbols .iter() .map(|x| SymbolPair::new(quote.clone(), x.clone())) .map(|x| PairStatus::new(x, 1, None)) .collect(), quote, - // account_info: String::new(), - // ledger: String::new(), trading_symbols, } } + pub fn with_strategy(mut self, strategy: Box) -> Self { + self.pair_statuses + .iter_mut() + .for_each(|x| x.set_strategy(dyn_clone::clone_box(&*strategy))); + + self + } + pub async fn current_prices(&self, symbol: Symbol) -> Result { let trading_pair = SymbolPair::new(self.quote.clone(), symbol.clone()); @@ -54,17 +59,35 @@ impl<'a> BfxBot<'a> { self.connector.current_prices(&trading_pair).await } - pub async fn update(&mut self) { - println!("Updating..."); - delay_for(self.ticker.duration()).await; - self.ticker.inc(); - // self.update_pairs().await; - println!("Done!"); + pub async fn start_loop(&mut self) -> Result<(), BoxError> { + if let Err(e) = self.update_pair_statuses().await { + println!("Error while updating pairs at first start: {}", e); + } + + loop { + self.update().await; + } } - // async fn update_pairs(&mut self) { - // let active_positions = self.bfx.positions.active_positions().await?; - // - // for p in active_positions {} - // } + async fn update(&mut self) { + delay_for(self.ticker.duration()).await; + self.ticker.inc(); + + if let Err(e) = self.update_pair_statuses().await { + println!("Error while updating pairs: {}", e); + } + } + + async fn update_pair_statuses(&mut self) -> Result<(), BoxError> { + for status in &mut self.pair_statuses { + // add positions for each pair + self.connector + .active_positions(status.pair()) + .await? + .into_iter() + .for_each(|x| status.add_position(x)); + } + + Ok(()) + } } diff --git a/rustybot/src/currency.rs b/rustybot/src/currency.rs index 97a8594..b5b0fd3 100644 --- a/rustybot/src/currency.rs +++ b/rustybot/src/currency.rs @@ -7,7 +7,7 @@ use regex::Regex; use crate::BoxError; -#[derive(Clone, PartialEq, Hash)] +#[derive(Clone, PartialEq, Hash, Debug, Eq)] pub struct Symbol { name: Cow<'static, str>, } @@ -26,6 +26,8 @@ impl Symbol { pub const BTC: Symbol = Symbol::new_static("BTC"); pub const ETH: Symbol = Symbol::new_static("ETH"); pub const LTC: Symbol = Symbol::new_static("LTC"); + pub const TESTBTC: Symbol = Symbol::new_static("TESTBTC"); + pub const TESTUSD: Symbol = Symbol::new_static("TESTUSD"); pub const USD: Symbol = Symbol::new_static("USD"); pub const GBP: Symbol = Symbol::new_static("GBP"); pub const EUR: Symbol = Symbol::new_static("EUR"); @@ -53,7 +55,7 @@ impl Display for Symbol { } } -#[derive(Clone)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct SymbolPair { quote: Symbol, base: Symbol, @@ -79,7 +81,7 @@ impl SymbolPair { impl Into for SymbolPair { fn into(self) -> String { - format!("{}{}", self.base, self.quote) + format!("{}/{}", self.base, self.quote) } } @@ -87,7 +89,7 @@ impl TryFrom<&str> for SymbolPair { type Error = BoxError; fn try_from(value: &str) -> Result { - const REGEX: &str = r"^[t|f](?P\w{3,4}):?(?P\w{3,4})"; + const REGEX: &str = r"^[t|f](?P\w{3,7}):?(?P\w{3,7})"; let captures = Regex::new(REGEX)?.captures(&value).ok_or("Invalid input")?; let quote = captures.name("quote").ok_or("Quote not found")?.as_str(); @@ -102,7 +104,7 @@ impl TryFrom<&str> for SymbolPair { impl Display for SymbolPair { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{}{}", self.base, self.quote) + write!(f, "{}/{}", self.base, self.quote) } } diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 9cb3bd0..83cbf17 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -8,13 +8,13 @@ use crate::pairs::PairStatus; use crate::positions::{Position, PositionProfitState, PositionState}; use crate::BoxError; -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub enum SignalKind { ClosePosition, OpenPosition, } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub struct EventMetadata { position_id: Option, order_id: Option, @@ -29,7 +29,7 @@ impl EventMetadata { } } -#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub enum EventKind { NewMinimum, NewMaximum, @@ -45,7 +45,7 @@ pub enum EventKind { Any, } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub struct Event { kind: EventKind, tick: u64, diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index 5885bdd..9025d11 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -1,7 +1,9 @@ use tokio::time::{delay_for, Duration}; use crate::bot::BfxBot; +use crate::connectors::BfxWrapper; use crate::currency::{Symbol, SymbolPair}; +use crate::strategy::TrailingStop; mod bot; mod connectors; @@ -17,24 +19,17 @@ pub type BoxError = Box; #[tokio::main] async fn main() -> Result<(), BoxError> { - // let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2"; - // let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli"; + let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2"; + let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli"; // - // let mut bot = BfxBot::new( - // test_api_key, - // test_api_secret, - // vec![Symbol::BTC, Symbol::ETH, Symbol::XMR], - // Symbol::USD, - // Duration::new(20, 0), - // ); - // - // loop { - // let ticker = bot.current_prices("ETH".into()).await?; - // bot.update().await; - // - // // let ticker = bot.current_prices("ETH".into()).await?; - // println!("{:?}", ticker); - // } + let bfx = BfxWrapper::new(test_api_key, test_api_secret); + let mut bot = BfxBot::new( + bfx, + vec![Symbol::TESTBTC], + Symbol::TESTUSD, + Duration::new(20, 0), + ) + .with_strategy(Box::new(TrailingStop::new())); - Ok(()) + Ok(bot.start_loop().await?) } diff --git a/rustybot/src/pairs.rs b/rustybot/src/pairs.rs index b2e986b..e5f779e 100644 --- a/rustybot/src/pairs.rs +++ b/rustybot/src/pairs.rs @@ -1,12 +1,12 @@ use std::collections::HashMap; use crate::currency::SymbolPair; -use crate::events::{Event, EventDispatcher}; +use crate::events::{Event, EventDispatcher, SignalKind}; use crate::orders::Order; use crate::positions::Position; use crate::strategy::Strategy; -pub struct PairStatus { +pub struct PairStatus<'a> { pair: SymbolPair, dispatcher: EventDispatcher, prices: HashMap, @@ -14,10 +14,11 @@ pub struct PairStatus { orders: HashMap>, positions: HashMap>, current_tick: u64, - strategy: Option>, + strategy: Option>, + signals: HashMap, } -impl PairStatus { +impl<'a> PairStatus<'a> { pub fn new(pair: SymbolPair, current_tick: u64, strategy: Option>) -> Self { PairStatus { pair, @@ -25,52 +26,82 @@ impl PairStatus { prices: HashMap::new(), events: Vec::new(), positions: HashMap::new(), + orders: HashMap::new(), + signals: HashMap::new(), current_tick, strategy, - orders: HashMap::new(), } } pub fn add_position(&mut self, position: Position) { - let (pw, events, signals) = { + let (new_position, events, signals) = { match &self.strategy { Some(strategy) => strategy.position_on_new_tick(&position, &self), - None => ( - position, - // PositionWrapper::new(position.clone(), position.pl(), position.pl_perc(), None), - vec![], - vec![], - ), + None => (position, vec![], vec![]), } }; self.positions .entry(self.current_tick) .or_default() - .push(pw.clone()); + .push(new_position.clone()); // calling position state callbacks - self.dispatcher.call_position_state_handlers(&pw, &self); + self.dispatcher + .call_position_state_handlers(&new_position, &self); // adding events and calling callbacks for e in events { self.add_event(e); } + + // adding signals to current tick vector + for s in signals { + self.add_signal(s); + } + + println!( + "EVENTS: {:?} | SIGNALS: {:?} | POSITION: {:?}", + self.events, self.signals, new_position + ); } - pub fn add_event(&mut self, event: Event) { + fn add_event(&mut self, event: Event) { self.events.push(event); self.dispatcher.call_event_handlers(&event, &self); } + fn add_signal(&mut self, signal: SignalKind) { + self.signals.insert(self.current_tick(), signal); + } + pub fn current_tick(&self) -> u64 { self.current_tick } - pub fn previous_pw(&self, id: u64) -> Option<&Position> { + pub fn set_strategy(&mut self, strategy: Box) { + self.strategy = Some(strategy); + } + + pub fn pair(&self) -> &SymbolPair { + &self.pair + } + + pub fn position_previous_tick(&self, id: u64, tick: Option) -> Option<&Position> { + let tick = match tick { + Some(tick) => { + if tick < 1 { + 1 + } else { + tick + } + } + None => self.current_tick() - 1, + }; + self.positions - .get(&(self.current_tick - 1)) + .get(&tick) .and_then(|x| x.iter().find(|x| x.position_id() == id)) } } diff --git a/rustybot/src/positions.rs b/rustybot/src/positions.rs index 444ad94..cbbd0af 100644 --- a/rustybot/src/positions.rs +++ b/rustybot/src/positions.rs @@ -1,6 +1,6 @@ use crate::currency::{Symbol, SymbolPair}; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Position { pair: SymbolPair, state: PositionState, @@ -91,7 +91,7 @@ impl Position { } } -#[derive(Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub enum PositionProfitState { Critical, Loss, @@ -111,7 +111,7 @@ impl PositionProfitState { } } -#[derive(Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub enum PositionState { Closed, Open, diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 24f3fdb..eeaab7d 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -3,8 +3,9 @@ use std::collections::HashMap; use crate::events::{Event, EventKind, EventMetadata, SignalKind}; use crate::pairs::PairStatus; use crate::positions::{Position, PositionProfitState, PositionState}; +use dyn_clone::DynClone; -pub trait Strategy { +pub trait Strategy: DynClone { fn position_on_new_tick( &self, position: &Position, @@ -12,7 +13,8 @@ pub trait Strategy { ) -> (Position, Vec, Vec); } -struct TrailingStop { +#[derive(Clone, Debug)] +pub struct TrailingStop { stop_percentages: HashMap, } @@ -62,18 +64,17 @@ impl Strategy for TrailingStop { } }; - let opt_pre_pw = status.previous_pw(position.position_id()); + let opt_pre_pw = status.position_previous_tick(position.position_id(), None); let event_metadata = EventMetadata::new(Some(position.position_id()), None); - // let pw = PositionWrapper::new(position.clone(), position.pl(), pl_perc, Some(state)); - let pw = position.clone(); + let new_position = position.clone().with_profit_state(Some(state)); match opt_pre_pw { Some(prev) => { if prev.profit_state() == Some(state) { - return (pw, events, signals); + return (new_position, events, signals); } } - None => return (pw, events, signals), + None => return (new_position, events, signals), }; let events = { @@ -114,6 +115,6 @@ impl Strategy for TrailingStop { events }; - return (pw, events, signals); + return (new_position, events, signals); } } -- 2.47.2 From 8442990a0e6fe07de7af2fbbcd0dd32467390050 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 6 Jan 2021 21:17:01 +0000 Subject: [PATCH 022/127] connector --- rustybot/src/connectors.rs | 93 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 rustybot/src/connectors.rs diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs new file mode 100644 index 0000000..d550a2f --- /dev/null +++ b/rustybot/src/connectors.rs @@ -0,0 +1,93 @@ +use std::convert::{TryFrom, TryInto}; + +use async_trait::async_trait; +use bitfinex::api::Bitfinex; +use bitfinex::ticker::TradingPairTicker; + +use crate::currency::{Symbol, SymbolPair}; +use crate::orders::Order; +use crate::positions::{Position, PositionState}; +use crate::BoxError; + +#[async_trait] +pub trait Connector { + async fn active_positions(&self, pair: &SymbolPair) -> Result, BoxError>; + async fn current_prices(&self, pair: &SymbolPair) -> Result; + async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError>; + async fn submit_order(&self) -> Result; +} + +pub struct BfxWrapper { + bfx: Bitfinex, + affiliate_code: Option, + // account_info: String, + // ledger: String, +} + +impl BfxWrapper { + pub fn new(api_key: &str, api_secret: &str) -> Self { + BfxWrapper { + bfx: Bitfinex::new(Some(api_key.into()), Some(api_secret.into())), + affiliate_code: None, + } + } + + pub fn with_affiliate_code(mut self, affiliate_code: Option) -> Self { + self.affiliate_code = affiliate_code; + self + } +} + +impl TryInto for bitfinex::positions::Position { + type Error = BoxError; + + fn try_into(self) -> Result { + let state = { + if self.status().to_lowercase().contains("active") { + PositionState::Open + } else { + PositionState::Closed + } + }; + + Ok(Position::new( + self.symbol().try_into()?, + state, + self.amount(), + self.base_price(), + self.pl(), + self.pl_perc(), + self.price_liq(), + self.position_id(), + ) + .with_creation_date(self.mts_create()) + .with_creation_update(self.mts_update())) + } +} + +#[async_trait] +impl Connector for BfxWrapper { + async fn active_positions(&self, pair: &SymbolPair) -> Result, BoxError> { + let active_positions = self.bfx.positions.active_positions().await?; + + Ok(active_positions + .into_iter() + .filter_map(|x| x.try_into().ok()) + .filter(|x: &Position| x.pair() == pair) + .collect()) + } + + async fn current_prices(&self, pair: &SymbolPair) -> Result { + let ticker: TradingPairTicker = self.bfx.ticker.trading_pair(pair.clone()).await?; + + Ok(ticker) + } + + async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError> { + unimplemented!() + } + + async fn submit_order(&self) -> Result { + unimplemented!() + } +} -- 2.47.2 From a029390c38cc1a9df4d006ce365491f449937f0f Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 11 Jan 2021 11:16:39 +0000 Subject: [PATCH 023/127] dispatcher getters --- rustybot/Cargo.lock | 53 ++++++++++++++++++++++++++++++++++++++++++- rustybot/src/pairs.rs | 7 ++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/rustybot/Cargo.lock b/rustybot/Cargo.lock index c51ed09..0b385b8 100644 --- a/rustybot/Cargo.lock +++ b/rustybot/Cargo.lock @@ -77,6 +77,8 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" name = "bitfinex" version = "0.4.3" dependencies = [ + "bitflags", + "chrono", "error-chain", "hex", "log 0.3.9", @@ -183,6 +185,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi 0.3.9", +] + [[package]] name = "core-foundation" version = "0.9.1" @@ -372,7 +387,7 @@ checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" dependencies = [ "cfg-if 0.1.10", "libc", - "wasi", + "wasi 0.9.0+wasi-snapshot-preview1", ] [[package]] @@ -765,6 +780,25 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.13.0" @@ -1239,6 +1273,17 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi 0.3.9", +] + [[package]] name = "tinyvec" version = "1.1.0" @@ -1513,6 +1558,12 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasm-bindgen" version = "0.2.69" diff --git a/rustybot/src/pairs.rs b/rustybot/src/pairs.rs index e5f779e..aa0fb46 100644 --- a/rustybot/src/pairs.rs +++ b/rustybot/src/pairs.rs @@ -33,6 +33,13 @@ impl<'a> PairStatus<'a> { } } + pub fn dispatcher(&self) -> &EventDispatcher { + &self.dispatcher + } + pub fn dispatcher_mut(&mut self) -> &mut EventDispatcher { + &mut self.dispatcher + } + pub fn add_position(&mut self, position: Position) { let (new_position, events, signals) = { match &self.strategy { -- 2.47.2 From 4d3a2ea8921c6e1aae2b8026feca5dc8559a6ecc Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 11 Jan 2021 11:17:01 +0000 Subject: [PATCH 024/127] explicit pair_statuses creation --- rustybot/src/bot.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index d7bba5a..69cfe85 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -8,6 +8,7 @@ use tokio::time::delay_for; use crate::connectors::Connector; use crate::currency::{Symbol, SymbolPair}; +use crate::events::EventKind; use crate::pairs::PairStatus; use crate::strategy::Strategy; use crate::ticker::Ticker; @@ -28,14 +29,16 @@ impl<'a> BfxBot<'a> { quote: Symbol, tick_duration: Duration, ) -> Self { + let pair_statuses = trading_symbols + .iter() + .map(|x| SymbolPair::new(quote.clone(), x.clone())) + .map(|x| PairStatus::new(x, 1, None)) + .collect(); + BfxBot { connector: Box::new(connector), ticker: Ticker::new(tick_duration), - pair_statuses: trading_symbols - .iter() - .map(|x| SymbolPair::new(quote.clone(), x.clone())) - .map(|x| PairStatus::new(x, 1, None)) - .collect(), + pair_statuses, quote, trading_symbols, } -- 2.47.2 From d2858ff0213d6d340b2ec0fece57795182d997f9 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 11 Jan 2021 17:15:59 +0000 Subject: [PATCH 025/127] added orderkind --- rustybot/src/orders.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/rustybot/src/orders.rs b/rustybot/src/orders.rs index 7569fa8..b307bc6 100644 --- a/rustybot/src/orders.rs +++ b/rustybot/src/orders.rs @@ -19,3 +19,19 @@ pub struct Order { pub hidden: i32, pub placed_id: Option, } + +pub enum OrderKind { + Limit, + ExchangeLimit, + Market, + ExchangeMarket, + Stop, + ExchangeStop, + StopLimit, + ExchangeStopLimit, + TrailingStop, + Fok, + ExchangeFok, + Ioc, + ExchangeIoc, +} -- 2.47.2 From 293ea60919857a06958080274856d01dad5f01f4 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 11 Jan 2021 17:16:17 +0000 Subject: [PATCH 026/127] using signals --- rustybot/src/events.rs | 24 +++++++++++++++++++++--- rustybot/src/strategy.rs | 4 +++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 83cbf17..c6a0fc3 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -8,9 +8,9 @@ use crate::pairs::PairStatus; use crate::positions::{Position, PositionProfitState, PositionState}; use crate::BoxError; -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub enum SignalKind { - ClosePosition, + ClosePosition { position_id: u64 }, OpenPosition, } @@ -80,7 +80,7 @@ pub struct EventDispatcher { event_handlers: HashMap JoinHandle<()>>>>, profit_state_handlers: HashMap JoinHandle<()>>>>, - + signal_handlers: HashMap JoinHandle<()>>>>, on_any_event_handlers: Vec JoinHandle<()>>>, on_any_profit_state_handlers: Vec JoinHandle<()>>>, } @@ -90,6 +90,7 @@ impl EventDispatcher { EventDispatcher { event_handlers: HashMap::new(), profit_state_handlers: HashMap::new(), + signal_handlers: HashMap::new(), on_any_event_handlers: Vec::new(), on_any_profit_state_handlers: Vec::new(), } @@ -157,4 +158,21 @@ impl EventDispatcher { .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))), } } + + pub fn register_signal_handler(&mut self, signal: SignalKind, f: F) + where + F: Fn(&SignalKind) -> Fut, + Fut: Future + Send, + { + match signal { + // PositionProfitState::Any => self + // .on_any_position_state_handlers + // .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))), + _ => self + .signal_handlers + .entry(signal) + .or_default() + .push(Box::new(move |s| tokio::spawn(f(s)))), + } + } } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index eeaab7d..af4e4c1 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -59,7 +59,9 @@ impl Strategy for TrailingStop { } else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 { PositionProfitState::Loss } else { - signals.push(SignalKind::ClosePosition); + signals.push(SignalKind::ClosePosition { + position_id: position.position_id(), + }); PositionProfitState::Critical } }; -- 2.47.2 From 8a7b3d4e222b36a31a530d8e8dc96ef72a0d4416 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 11 Jan 2021 17:16:44 +0000 Subject: [PATCH 027/127] order submission --- rustybot/src/connectors.rs | 52 +++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index d550a2f..bc8647d 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -2,19 +2,31 @@ use std::convert::{TryFrom, TryInto}; use async_trait::async_trait; use bitfinex::api::Bitfinex; +use bitfinex::orders::{OrderForm, OrderMeta}; use bitfinex::ticker::TradingPairTicker; use crate::currency::{Symbol, SymbolPair}; -use crate::orders::Order; +use crate::orders::{Order, OrderKind}; use crate::positions::{Position, PositionState}; use crate::BoxError; +#[derive(Eq, PartialEq, Hash, Clone)] +pub enum ExchangeKind { + Bitfinex, +} + #[async_trait] pub trait Connector { async fn active_positions(&self, pair: &SymbolPair) -> Result, BoxError>; async fn current_prices(&self, pair: &SymbolPair) -> Result; async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError>; - async fn submit_order(&self) -> Result; + async fn submit_order( + &self, + pair: &SymbolPair, + amount: f64, + price: f64, + kind: &OrderKind, + ) -> Result<(), BoxError>; } pub struct BfxWrapper { @@ -65,6 +77,26 @@ impl TryInto for bitfinex::positions::Position { } } +impl From<&OrderKind> for bitfinex::orders::OrderKind { + fn from(o: &OrderKind) -> Self { + match o { + OrderKind::Limit => Self::Limit, + OrderKind::ExchangeLimit => Self::ExchangeLimit, + OrderKind::Market => Self::Market, + OrderKind::ExchangeMarket => Self::ExchangeMarket, + OrderKind::Stop => Self::Stop, + OrderKind::ExchangeStop => Self::ExchangeStop, + OrderKind::StopLimit => Self::StopLimit, + OrderKind::ExchangeStopLimit => Self::ExchangeStopLimit, + OrderKind::TrailingStop => Self::TrailingStop, + OrderKind::Fok => Self::Fok, + OrderKind::ExchangeFok => Self::ExchangeFok, + OrderKind::Ioc => Self::Ioc, + OrderKind::ExchangeIoc => Self::ExchangeIoc, + } + } +} + #[async_trait] impl Connector for BfxWrapper { async fn active_positions(&self, pair: &SymbolPair) -> Result, BoxError> { @@ -87,7 +119,19 @@ impl Connector for BfxWrapper { unimplemented!() } - async fn submit_order(&self) -> Result { - unimplemented!() + async fn submit_order( + &self, + pair: &SymbolPair, + amount: f64, + price: f64, + kind: &OrderKind, + ) -> Result<(), BoxError> { + let order_form = match &self.affiliate_code { + Some(affiliate_code) => OrderForm::new(pair.trading_repr(), price, amount, kind.into()) + .with_meta(OrderMeta::new(affiliate_code.clone())), + None => OrderForm::new(pair.trading_repr(), price, amount, kind.into()), + }; + + Ok(self.bfx.orders.submit_order(&order_form).await?) } } -- 2.47.2 From 3171e054e02dc72a9aa304599ee3f4f065a1bf29 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 11 Jan 2021 18:40:19 +0000 Subject: [PATCH 028/127] signal handler --- rustybot/src/events.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index c6a0fc3..781ca60 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -4,6 +4,7 @@ use std::future::Future; use tokio::stream::StreamExt; use tokio::task::JoinHandle; +use crate::bot::BfxBot; use crate::pairs::PairStatus; use crate::positions::{Position, PositionProfitState, PositionState}; use crate::BoxError; @@ -96,6 +97,14 @@ impl EventDispatcher { } } + pub fn call_signal_handlers(&self, signal: &SignalKind) { + if let Some(functions) = self.signal_handlers.get(&signal) { + for f in functions { + f(signal); + } + } + } + pub fn call_event_handlers(&self, event: &Event, status: &PairStatus) { if let Some(functions) = self.event_handlers.get(&event.kind()) { for f in functions { -- 2.47.2 From e8642d758e946c5b7c5de5911ad33e2495ba06a3 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 13 Jan 2021 08:57:36 +0000 Subject: [PATCH 029/127] removed positions and orders modules. created models --- rustybot/src/connectors.rs | 3 +- rustybot/src/events.rs | 2 +- rustybot/src/main.rs | 10 ++-- rustybot/src/orders.rs | 37 ------------ rustybot/src/pairs.rs | 3 +- rustybot/src/positions.rs | 118 ------------------------------------- rustybot/src/strategy.rs | 2 +- 7 files changed, 10 insertions(+), 165 deletions(-) delete mode 100644 rustybot/src/orders.rs delete mode 100644 rustybot/src/positions.rs diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index bc8647d..e1591b5 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -6,8 +6,7 @@ use bitfinex::orders::{OrderForm, OrderMeta}; use bitfinex::ticker::TradingPairTicker; use crate::currency::{Symbol, SymbolPair}; -use crate::orders::{Order, OrderKind}; -use crate::positions::{Position, PositionState}; +use crate::models::{Order, OrderKind, Position, PositionState}; use crate::BoxError; #[derive(Eq, PartialEq, Hash, Clone)] diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 781ca60..c32b0ae 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -5,8 +5,8 @@ use tokio::stream::StreamExt; use tokio::task::JoinHandle; use crate::bot::BfxBot; +use crate::models::{Position, PositionProfitState}; use crate::pairs::PairStatus; -use crate::positions::{Position, PositionProfitState, PositionState}; use crate::BoxError; #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index 9025d11..501ccae 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -3,15 +3,16 @@ use tokio::time::{delay_for, Duration}; use crate::bot::BfxBot; use crate::connectors::BfxWrapper; use crate::currency::{Symbol, SymbolPair}; +use crate::events::SignalKind; use crate::strategy::TrailingStop; +use std::thread::JoinHandle; mod bot; mod connectors; mod currency; mod events; -mod orders; +mod models; mod pairs; -mod positions; mod strategy; mod ticker; @@ -21,8 +22,9 @@ pub type BoxError = Box; async fn main() -> Result<(), BoxError> { let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2"; let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli"; - // - let bfx = BfxWrapper::new(test_api_key, test_api_secret); + let bfx = BfxWrapper::new(test_api_key, test_api_secret) + .with_affiliate_code(Some("XPebOgHxA".into())); + let mut bot = BfxBot::new( bfx, vec![Symbol::TESTBTC], diff --git a/rustybot/src/orders.rs b/rustybot/src/orders.rs deleted file mode 100644 index b307bc6..0000000 --- a/rustybot/src/orders.rs +++ /dev/null @@ -1,37 +0,0 @@ -pub struct Order { - pub id: i64, - pub group_id: Option, - pub client_id: i64, - pub symbol: String, - pub creation_timestamp: i64, - pub update_timestamp: i64, - pub amount: f64, - pub amount_original: f64, - pub order_type: String, - pub previous_order_type: Option, - pub flags: Option, - pub order_status: Option, - pub price: f64, - pub price_avg: f64, - pub price_trailing: Option, - pub price_aux_limit: Option, - pub notify: i32, - pub hidden: i32, - pub placed_id: Option, -} - -pub enum OrderKind { - Limit, - ExchangeLimit, - Market, - ExchangeMarket, - Stop, - ExchangeStop, - StopLimit, - ExchangeStopLimit, - TrailingStop, - Fok, - ExchangeFok, - Ioc, - ExchangeIoc, -} diff --git a/rustybot/src/pairs.rs b/rustybot/src/pairs.rs index aa0fb46..903285c 100644 --- a/rustybot/src/pairs.rs +++ b/rustybot/src/pairs.rs @@ -2,8 +2,7 @@ use std::collections::HashMap; use crate::currency::SymbolPair; use crate::events::{Event, EventDispatcher, SignalKind}; -use crate::orders::Order; -use crate::positions::Position; +use crate::models::{Order, Position}; use crate::strategy::Strategy; pub struct PairStatus<'a> { diff --git a/rustybot/src/positions.rs b/rustybot/src/positions.rs deleted file mode 100644 index cbbd0af..0000000 --- a/rustybot/src/positions.rs +++ /dev/null @@ -1,118 +0,0 @@ -use crate::currency::{Symbol, SymbolPair}; - -#[derive(Clone, Debug)] -pub struct Position { - pair: SymbolPair, - state: PositionState, - profit_state: Option, - amount: f64, - base_price: f64, - pl: f64, - pl_perc: f64, - price_liq: f64, - position_id: u64, - creation_date: Option, - creation_update: Option, -} - -impl Position { - pub fn new( - pair: SymbolPair, - state: PositionState, - amount: f64, - base_price: f64, - pl: f64, - pl_perc: f64, - price_liq: f64, - position_id: u64, - ) -> Self { - Position { - pair, - state, - amount, - base_price, - pl, - pl_perc, - price_liq, - position_id, - creation_date: None, - creation_update: None, - profit_state: None, - } - } - - pub fn with_creation_date(mut self, creation_date: Option) -> Self { - self.creation_date = creation_date; - self - } - - pub fn with_creation_update(mut self, creation_update: Option) -> Self { - self.creation_update = creation_update; - self - } - - pub fn with_profit_state(mut self, profit_state: Option) -> Self { - self.profit_state = profit_state; - self - } - - pub fn pair(&self) -> &SymbolPair { - &self.pair - } - pub fn state(&self) -> PositionState { - self.state - } - pub fn amount(&self) -> f64 { - self.amount - } - pub fn base_price(&self) -> f64 { - self.base_price - } - pub fn pl(&self) -> f64 { - self.pl - } - pub fn pl_perc(&self) -> f64 { - self.pl_perc - } - pub fn price_liq(&self) -> f64 { - self.price_liq - } - pub fn position_id(&self) -> u64 { - self.position_id - } - pub fn profit_state(&self) -> Option { - self.profit_state - } - pub fn creation_date(&self) -> Option { - self.creation_date - } - pub fn creation_update(&self) -> Option { - self.creation_update - } -} - -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] -pub enum PositionProfitState { - Critical, - Loss, - BreakEven, - MinimumProfit, - Profit, -} - -impl PositionProfitState { - fn color(self) -> String { - match self { - PositionProfitState::Critical | PositionProfitState::Loss => "red", - PositionProfitState::BreakEven => "yellow", - PositionProfitState::MinimumProfit | PositionProfitState::Profit => "green", - } - .into() - } -} - -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] -pub enum PositionState { - Closed, - Open, -} diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index af4e4c1..8a67940 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; use crate::events::{Event, EventKind, EventMetadata, SignalKind}; +use crate::models::{Position, PositionProfitState}; use crate::pairs::PairStatus; -use crate::positions::{Position, PositionProfitState, PositionState}; use dyn_clone::DynClone; pub trait Strategy: DynClone { -- 2.47.2 From 7e8a5cc580eb3b1b83b5d6e238a88489d1d50c63 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 13 Jan 2021 08:57:46 +0000 Subject: [PATCH 030/127] removed positions and orders modules. created models --- rustybot/src/models.rs | 156 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 rustybot/src/models.rs diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs new file mode 100644 index 0000000..674b1bc --- /dev/null +++ b/rustybot/src/models.rs @@ -0,0 +1,156 @@ +use crate::currency::SymbolPair; + +pub struct Order { + pub id: i64, + pub group_id: Option, + pub client_id: i64, + pub symbol: String, + pub creation_timestamp: i64, + pub update_timestamp: i64, + pub amount: f64, + pub amount_original: f64, + pub order_type: String, + pub previous_order_type: Option, + pub flags: Option, + pub order_status: Option, + pub price: f64, + pub price_avg: f64, + pub price_trailing: Option, + pub price_aux_limit: Option, + pub notify: i32, + pub hidden: i32, + pub placed_id: Option, +} + +pub enum OrderKind { + Limit, + ExchangeLimit, + Market, + ExchangeMarket, + Stop, + ExchangeStop, + StopLimit, + ExchangeStopLimit, + TrailingStop, + Fok, + ExchangeFok, + Ioc, + ExchangeIoc, +} + +#[derive(Clone, Debug)] +pub struct Position { + pair: SymbolPair, + state: PositionState, + profit_state: Option, + amount: f64, + base_price: f64, + pl: f64, + pl_perc: f64, + price_liq: f64, + position_id: u64, + creation_date: Option, + creation_update: Option, +} + +impl Position { + pub fn new( + pair: SymbolPair, + state: PositionState, + amount: f64, + base_price: f64, + pl: f64, + pl_perc: f64, + price_liq: f64, + position_id: u64, + ) -> Self { + Position { + pair, + state, + amount, + base_price, + pl, + pl_perc, + price_liq, + position_id, + creation_date: None, + creation_update: None, + profit_state: None, + } + } + + pub fn with_creation_date(mut self, creation_date: Option) -> Self { + self.creation_date = creation_date; + self + } + + pub fn with_creation_update(mut self, creation_update: Option) -> Self { + self.creation_update = creation_update; + self + } + + pub fn with_profit_state(mut self, profit_state: Option) -> Self { + self.profit_state = profit_state; + self + } + + pub fn pair(&self) -> &SymbolPair { + &self.pair + } + pub fn state(&self) -> PositionState { + self.state + } + pub fn amount(&self) -> f64 { + self.amount + } + pub fn base_price(&self) -> f64 { + self.base_price + } + pub fn pl(&self) -> f64 { + self.pl + } + pub fn pl_perc(&self) -> f64 { + self.pl_perc + } + pub fn price_liq(&self) -> f64 { + self.price_liq + } + pub fn position_id(&self) -> u64 { + self.position_id + } + pub fn profit_state(&self) -> Option { + self.profit_state + } + pub fn creation_date(&self) -> Option { + self.creation_date + } + pub fn creation_update(&self) -> Option { + self.creation_update + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub enum PositionProfitState { + Critical, + Loss, + BreakEven, + MinimumProfit, + Profit, +} + +impl PositionProfitState { + fn color(self) -> String { + match self { + PositionProfitState::Critical | PositionProfitState::Loss => "red", + PositionProfitState::BreakEven => "yellow", + PositionProfitState::MinimumProfit | PositionProfitState::Profit => "green", + } + .into() + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub enum PositionState { + Closed, + Open, +} -- 2.47.2 From 4801d93bfe7ab39666bb0b22ebaee5701944f739 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 13 Jan 2021 08:59:13 +0000 Subject: [PATCH 031/127] Strategy to PositionStrategy refactoring --- rustybot/src/bot.rs | 4 ++-- rustybot/src/pairs.rs | 14 +++++++++----- rustybot/src/strategy.rs | 8 ++++---- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index 69cfe85..886d224 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -10,7 +10,7 @@ use crate::connectors::Connector; use crate::currency::{Symbol, SymbolPair}; use crate::events::EventKind; use crate::pairs::PairStatus; -use crate::strategy::Strategy; +use crate::strategy::PositionStrategy; use crate::ticker::Ticker; use crate::BoxError; @@ -44,7 +44,7 @@ impl<'a> BfxBot<'a> { } } - pub fn with_strategy(mut self, strategy: Box) -> Self { + pub fn with_strategy(mut self, strategy: Box) -> Self { self.pair_statuses .iter_mut() .for_each(|x| x.set_strategy(dyn_clone::clone_box(&*strategy))); diff --git a/rustybot/src/pairs.rs b/rustybot/src/pairs.rs index 903285c..479efe5 100644 --- a/rustybot/src/pairs.rs +++ b/rustybot/src/pairs.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use crate::currency::SymbolPair; use crate::events::{Event, EventDispatcher, SignalKind}; use crate::models::{Order, Position}; -use crate::strategy::Strategy; +use crate::strategy::PositionStrategy; pub struct PairStatus<'a> { pair: SymbolPair, @@ -13,12 +13,16 @@ pub struct PairStatus<'a> { orders: HashMap>, positions: HashMap>, current_tick: u64, - strategy: Option>, + strategy: Option>, signals: HashMap, } impl<'a> PairStatus<'a> { - pub fn new(pair: SymbolPair, current_tick: u64, strategy: Option>) -> Self { + pub fn new( + pair: SymbolPair, + current_tick: u64, + strategy: Option>, + ) -> Self { PairStatus { pair, dispatcher: EventDispatcher::new(), @@ -42,7 +46,7 @@ impl<'a> PairStatus<'a> { pub fn add_position(&mut self, position: Position) { let (new_position, events, signals) = { match &self.strategy { - Some(strategy) => strategy.position_on_new_tick(&position, &self), + Some(strategy) => strategy.on_new_tick(&position, &self), None => (position, vec![], vec![]), } }; @@ -86,7 +90,7 @@ impl<'a> PairStatus<'a> { self.current_tick } - pub fn set_strategy(&mut self, strategy: Box) { + pub fn set_strategy(&mut self, strategy: Box) { self.strategy = Some(strategy); } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 8a67940..658dcb7 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -5,8 +5,8 @@ use crate::models::{Position, PositionProfitState}; use crate::pairs::PairStatus; use dyn_clone::DynClone; -pub trait Strategy: DynClone { - fn position_on_new_tick( +pub trait PositionStrategy: DynClone { + fn on_new_tick( &self, position: &Position, status: &PairStatus, @@ -37,8 +37,8 @@ impl TrailingStop { } } -impl Strategy for TrailingStop { - fn position_on_new_tick( +impl PositionStrategy for TrailingStop { + fn on_new_tick( &self, position: &Position, status: &PairStatus, -- 2.47.2 From bb518e62599607ac3b6a5f4d60f60c73e2083e10 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 13 Jan 2021 09:03:24 +0000 Subject: [PATCH 032/127] attached managers (no implementation) --- rustybot/src/main.rs | 1 + rustybot/src/managers.rs | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 rustybot/src/managers.rs diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index 501ccae..dea442d 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -11,6 +11,7 @@ mod bot; mod connectors; mod currency; mod events; +mod managers; mod models; mod pairs; mod strategy; diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs new file mode 100644 index 0000000..11e5758 --- /dev/null +++ b/rustybot/src/managers.rs @@ -0,0 +1,5 @@ +struct EventManager {} + +struct PositionManager {} + +struct OrderManager {} -- 2.47.2 From 0ea8a55a7f7f1411387a7b7d34a0734da07d4ba9 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 13 Jan 2021 09:04:58 +0000 Subject: [PATCH 033/127] BfxWrapper -> BfxConnector --- rustybot/src/connectors.rs | 8 ++++---- rustybot/src/main.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index e1591b5..2abbfbc 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -28,16 +28,16 @@ pub trait Connector { ) -> Result<(), BoxError>; } -pub struct BfxWrapper { +pub struct BitfinexConnector { bfx: Bitfinex, affiliate_code: Option, // account_info: String, // ledger: String, } -impl BfxWrapper { +impl BitfinexConnector { pub fn new(api_key: &str, api_secret: &str) -> Self { - BfxWrapper { + BitfinexConnector { bfx: Bitfinex::new(Some(api_key.into()), Some(api_secret.into())), affiliate_code: None, } @@ -97,7 +97,7 @@ impl From<&OrderKind> for bitfinex::orders::OrderKind { } #[async_trait] -impl Connector for BfxWrapper { +impl Connector for BitfinexConnector { async fn active_positions(&self, pair: &SymbolPair) -> Result, BoxError> { let active_positions = self.bfx.positions.active_positions().await?; diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index dea442d..22ce0de 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -1,7 +1,7 @@ use tokio::time::{delay_for, Duration}; use crate::bot::BfxBot; -use crate::connectors::BfxWrapper; +use crate::connectors::BitfinexConnector; use crate::currency::{Symbol, SymbolPair}; use crate::events::SignalKind; use crate::strategy::TrailingStop; @@ -23,7 +23,7 @@ pub type BoxError = Box; async fn main() -> Result<(), BoxError> { let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2"; let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli"; - let bfx = BfxWrapper::new(test_api_key, test_api_secret) + let bfx = BitfinexConnector::new(test_api_key, test_api_secret) .with_affiliate_code(Some("XPebOgHxA".into())); let mut bot = BfxBot::new( -- 2.47.2 From 957d3e32b85756953cb2de20925c9ef8188534f6 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 13 Jan 2021 09:24:59 +0000 Subject: [PATCH 034/127] created Client to wrap Connectors --- rustybot/src/connectors.rs | 30 +++++++++++++++++++++++++++++- rustybot/src/managers.rs | 17 +++++++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 2abbfbc..09ec11e 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -8,10 +8,38 @@ use bitfinex::ticker::TradingPairTicker; use crate::currency::{Symbol, SymbolPair}; use crate::models::{Order, OrderKind, Position, PositionState}; use crate::BoxError; +use std::sync::Arc; #[derive(Eq, PartialEq, Hash, Clone)] pub enum ExchangeKind { - Bitfinex, + Bitfinex { + api_key: String, + api_secret: String, + affiliate_code: Option, + }, +} + +/// You do **not** have to wrap the `Client` it in an [`Rc`] or [`Arc`] to **reuse** it, +/// because it already uses an [`Arc`] internally. +#[derive(Clone)] +pub struct Client { + inner: Arc>, +} + +impl Client { + pub fn new(exchange: ExchangeKind) -> Self { + let inner = match exchange { + ExchangeKind::Bitfinex { + api_key, + api_secret, + affiliate_code, + } => BitfinexConnector::new(&api_key, &api_secret).with_affiliate_code(affiliate_code), + }; + + Client { + inner: Arc::new(Box::new(inner)), + } + } } #[async_trait] diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 11e5758..997a033 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -1,5 +1,18 @@ +use crate::connectors::{Client, Connector}; +use crate::models::{Order, Position}; +use crate::ticker::Ticker; +use std::collections::VecDeque; + struct EventManager {} -struct PositionManager {} +struct PositionManager { + queue: VecDeque, + open_positions: Vec, + client: Client, +} -struct OrderManager {} +struct OrderManager { + queue: VecDeque, + open_orders: Vec, + client: Client, +} -- 2.47.2 From b677cb880fd54f5a20cbec348876af42435ef1cc Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 13 Jan 2021 09:26:29 +0000 Subject: [PATCH 035/127] EventManager has events --- rustybot/src/managers.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 997a033..c896632 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -1,9 +1,12 @@ use crate::connectors::{Client, Connector}; +use crate::events::Event; use crate::models::{Order, Position}; use crate::ticker::Ticker; -use std::collections::VecDeque; +use std::collections::{HashMap, VecDeque}; -struct EventManager {} +struct EventManager { + events: Vec, +} struct PositionManager { queue: VecDeque, -- 2.47.2 From 2c2f164e187fc7e0fe799e616ee54f0b45b83249 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Thu, 14 Jan 2021 12:42:23 +0000 Subject: [PATCH 036/127] price manager working --- rustybot/src/bot.rs | 116 +++++++++++--------- rustybot/src/connectors.rs | 155 ++++++++++++++++++-------- rustybot/src/events.rs | 18 +-- rustybot/src/main.rs | 24 ++-- rustybot/src/managers.rs | 45 +++++++- rustybot/src/models.rs | 28 +++++ rustybot/src/pairs.rs | 208 ++++++++++++++++++++--------------- rustybot/src/strategy.rs | 218 ++++++++++++++++++------------------- 8 files changed, 495 insertions(+), 317 deletions(-) diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index 886d224..e832c3e 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -4,93 +4,107 @@ use std::collections::HashMap; use bitfinex::api::Bitfinex; use bitfinex::positions::Position; use bitfinex::ticker::TradingPairTicker; +use futures_util::stream::FuturesUnordered; use tokio::time::delay_for; -use crate::connectors::Connector; +use crate::connectors::{Client, Connector, ExchangeKind}; use crate::currency::{Symbol, SymbolPair}; -use crate::events::EventKind; -use crate::pairs::PairStatus; +use crate::events::{Event, EventKind}; +use crate::managers::{OrderManager, PositionManager}; +use crate::pairs::PriceManager; use crate::strategy::PositionStrategy; use crate::ticker::Ticker; use crate::BoxError; +use tokio::stream::StreamExt; -pub struct BfxBot<'a> { - connector: Box, +pub struct BfxBot { ticker: Ticker, - pair_statuses: Vec>, quote: Symbol, trading_symbols: Vec, + price_managers: Vec, + order_managers: Vec, + pos_managers: Vec, } -impl<'a> BfxBot<'a> { - pub fn new( - connector: C, +impl BfxBot { + pub fn new( + exchanges: Vec, trading_symbols: Vec, quote: Symbol, tick_duration: Duration, ) -> Self { - let pair_statuses = trading_symbols - .iter() - .map(|x| SymbolPair::new(quote.clone(), x.clone())) - .map(|x| PairStatus::new(x, 1, None)) - .collect(); + let clients: Vec<_> = exchanges.iter().map(|x| Client::new(x)).collect(); + + let mut pos_managers = Vec::new(); + let mut order_managers = Vec::new(); + let mut pair_statuses = Vec::new(); + + for c in clients { + pos_managers.push(PositionManager::new(c.clone())); + order_managers.push(OrderManager::new(c.clone())); + pair_statuses.extend( + trading_symbols + .iter() + .map(|x| SymbolPair::new(quote.clone(), x.clone())) + .map(|x| PriceManager::new(x, c.clone())) + .collect::>(), + ) + } BfxBot { - connector: Box::new(connector), ticker: Ticker::new(tick_duration), - pair_statuses, + price_managers: pair_statuses, quote, trading_symbols, + order_managers, + pos_managers, } } - pub fn with_strategy(mut self, strategy: Box) -> Self { - self.pair_statuses - .iter_mut() - .for_each(|x| x.set_strategy(dyn_clone::clone_box(&*strategy))); - - self - } - - pub async fn current_prices(&self, symbol: Symbol) -> Result { - let trading_pair = SymbolPair::new(self.quote.clone(), symbol.clone()); - - if !self.trading_symbols.contains(&symbol) { - return Err("Symbol not supported.".into()); - } - - self.connector.current_prices(&trading_pair).await - } - pub async fn start_loop(&mut self) -> Result<(), BoxError> { - if let Err(e) = self.update_pair_statuses().await { - println!("Error while updating pairs at first start: {}", e); - } - loop { self.update().await; } } - async fn update(&mut self) { + async fn update<'a>(&'a mut self) { delay_for(self.ticker.duration()).await; self.ticker.inc(); - if let Err(e) = self.update_pair_statuses().await { - println!("Error while updating pairs: {}", e); - } + self.update_price_managers().await.unwrap(); } - async fn update_pair_statuses(&mut self) -> Result<(), BoxError> { - for status in &mut self.pair_statuses { - // add positions for each pair - self.connector - .active_positions(status.pair()) - .await? - .into_iter() - .for_each(|x| status.add_position(x)); + async fn update_price_managers(&mut self) -> Result>, BoxError> { + let futures: Vec<_> = self + .price_managers + .clone() + .into_iter() + // the only reason you need the async block is that the future + // returned by x.update(tick) borrows from x + // so we create a future that first takes ownership of x, then uses it to call x.update + .map(|mut x| { + let tick = self.ticker.current_tick(); + async move { x.update(tick).await } + }) + .map(tokio::spawn) + .collect(); + + let mut price_entries = vec![]; + + for f in futures { + price_entries.push(f.await??); } - Ok(()) + for mut manager in &mut self.price_managers { + let prices: Vec<_> = price_entries + .drain_filter(|x| x.pair() == manager.pair()) + .collect(); + + for p in prices { + manager.add_entry(p); + } + } + + Ok(None) } } diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 09ec11e..bbe5725 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -1,4 +1,5 @@ use std::convert::{TryFrom, TryInto}; +use std::sync::Arc; use async_trait::async_trait; use bitfinex::api::Bitfinex; @@ -6,11 +7,11 @@ use bitfinex::orders::{OrderForm, OrderMeta}; use bitfinex::ticker::TradingPairTicker; use crate::currency::{Symbol, SymbolPair}; -use crate::models::{Order, OrderKind, Position, PositionState}; +use crate::models::{Order, OrderKind, Position, PositionState, PriceTicker}; use crate::BoxError; -use std::sync::Arc; +use std::fmt::{Debug, Formatter}; -#[derive(Eq, PartialEq, Hash, Clone)] +#[derive(Eq, PartialEq, Hash, Clone, Debug)] pub enum ExchangeKind { Bitfinex { api_key: String, @@ -19,31 +20,56 @@ pub enum ExchangeKind { }, } -/// You do **not** have to wrap the `Client` it in an [`Rc`] or [`Arc`] to **reuse** it, +/// You do **not** have to wrap the `Client` in an [`Rc`] or [`Arc`] to **reuse** it, /// because it already uses an [`Arc`] internally. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Client { + exchange: ExchangeKind, inner: Arc>, } impl Client { - pub fn new(exchange: ExchangeKind) -> Self { - let inner = match exchange { + pub fn new(exchange: &ExchangeKind) -> Self { + let inner = match &exchange { ExchangeKind::Bitfinex { api_key, api_secret, affiliate_code, - } => BitfinexConnector::new(&api_key, &api_secret).with_affiliate_code(affiliate_code), + } => BitfinexConnector::new(&api_key, &api_secret) + .with_affiliate_code(affiliate_code.clone()), }; Client { + exchange: exchange.clone(), inner: Arc::new(Box::new(inner)), } } + + pub async fn active_positions(&self, pair: &SymbolPair) -> Result, BoxError> { + self.inner.active_positions(pair).await + } + + pub async fn current_prices(&self, pair: &SymbolPair) -> Result { + self.inner.current_prices(pair).await + } + + pub async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError> { + self.inner.active_orders(pair).await + } + + pub async fn submit_order( + &self, + pair: &SymbolPair, + amount: f64, + price: f64, + kind: &OrderKind, + ) -> Result<(), BoxError> { + self.inner.submit_order(pair, amount, price, kind).await + } } #[async_trait] -pub trait Connector { +pub trait Connector: Send + Sync { async fn active_positions(&self, pair: &SymbolPair) -> Result, BoxError>; async fn current_prices(&self, pair: &SymbolPair) -> Result; async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError>; @@ -56,6 +82,16 @@ pub trait Connector { ) -> Result<(), BoxError>; } +impl Debug for dyn Connector { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "Connector") + } +} + +/************** +* BITFINEX +**************/ + pub struct BitfinexConnector { bfx: Bitfinex, affiliate_code: Option, @@ -75,6 +111,57 @@ impl BitfinexConnector { self.affiliate_code = affiliate_code; self } + + fn format_trading_pair(&self, pair: &SymbolPair) -> String { + if pair.to_string().to_lowercase().contains("test") { + format!("{}:{}", pair.base(), pair.quote()) + } else { + format!("{}{}", pair.base(), pair.quote()) + } + } +} + +#[async_trait] +impl Connector for BitfinexConnector { + async fn active_positions(&self, pair: &SymbolPair) -> Result, BoxError> { + let active_positions = self.bfx.positions.active_positions().await?; + + Ok(active_positions + .into_iter() + .filter_map(|x| x.try_into().ok()) + .filter(|x: &Position| x.pair() == pair) + .collect()) + } + + async fn current_prices(&self, pair: &SymbolPair) -> Result { + let ticker: TradingPairTicker = self + .bfx + .ticker + .trading_pair(self.format_trading_pair(pair)) + .await?; + + Ok(ticker) + } + + async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError> { + unimplemented!() + } + + async fn submit_order( + &self, + pair: &SymbolPair, + amount: f64, + price: f64, + kind: &OrderKind, + ) -> Result<(), BoxError> { + let order_form = match &self.affiliate_code { + Some(affiliate_code) => OrderForm::new(pair.trading_repr(), price, amount, kind.into()) + .with_meta(OrderMeta::new(affiliate_code.clone())), + None => OrderForm::new(pair.trading_repr(), price, amount, kind.into()), + }; + + Ok(self.bfx.orders.submit_order(&order_form).await?) + } } impl TryInto for bitfinex::positions::Position { @@ -124,41 +211,19 @@ impl From<&OrderKind> for bitfinex::orders::OrderKind { } } -#[async_trait] -impl Connector for BitfinexConnector { - async fn active_positions(&self, pair: &SymbolPair) -> Result, BoxError> { - let active_positions = self.bfx.positions.active_positions().await?; - - Ok(active_positions - .into_iter() - .filter_map(|x| x.try_into().ok()) - .filter(|x: &Position| x.pair() == pair) - .collect()) - } - - async fn current_prices(&self, pair: &SymbolPair) -> Result { - let ticker: TradingPairTicker = self.bfx.ticker.trading_pair(pair.clone()).await?; - - Ok(ticker) - } - - async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError> { - unimplemented!() - } - - async fn submit_order( - &self, - pair: &SymbolPair, - amount: f64, - price: f64, - kind: &OrderKind, - ) -> Result<(), BoxError> { - let order_form = match &self.affiliate_code { - Some(affiliate_code) => OrderForm::new(pair.trading_repr(), price, amount, kind.into()) - .with_meta(OrderMeta::new(affiliate_code.clone())), - None => OrderForm::new(pair.trading_repr(), price, amount, kind.into()), - }; - - Ok(self.bfx.orders.submit_order(&order_form).await?) +impl From for PriceTicker { + fn from(t: TradingPairTicker) -> Self { + Self { + bid: t.bid, + bid_size: t.bid_size, + ask: t.ask, + ask_size: t.ask_size, + daily_change: t.daily_change, + daily_change_perc: t.daily_change_perc, + last_price: t.last_price, + volume: t.volume, + high: t.high, + low: t.low, + } } } diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index c32b0ae..1fdfa14 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -6,7 +6,7 @@ use tokio::task::JoinHandle; use crate::bot::BfxBot; use crate::models::{Position, PositionProfitState}; -use crate::pairs::PairStatus; +use crate::pairs::PriceManager; use crate::BoxError; #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] @@ -78,12 +78,12 @@ impl Event { } pub struct EventDispatcher { - event_handlers: HashMap JoinHandle<()>>>>, + event_handlers: HashMap JoinHandle<()>>>>, profit_state_handlers: - HashMap JoinHandle<()>>>>, + HashMap JoinHandle<()>>>>, signal_handlers: HashMap JoinHandle<()>>>>, - on_any_event_handlers: Vec JoinHandle<()>>>, - on_any_profit_state_handlers: Vec JoinHandle<()>>>, + on_any_event_handlers: Vec JoinHandle<()>>>, + on_any_profit_state_handlers: Vec JoinHandle<()>>>, } impl EventDispatcher { @@ -105,7 +105,7 @@ impl EventDispatcher { } } - pub fn call_event_handlers(&self, event: &Event, status: &PairStatus) { + pub fn call_event_handlers(&self, event: &Event, status: &PriceManager) { if let Some(functions) = self.event_handlers.get(&event.kind()) { for f in functions { f(event, status); @@ -117,7 +117,7 @@ impl EventDispatcher { } } - pub fn call_position_state_handlers(&self, position: &Position, status: &PairStatus) { + pub fn call_position_state_handlers(&self, position: &Position, status: &PriceManager) { if let Some(profit_state) = &position.profit_state() { if let Some(functions) = self.profit_state_handlers.get(profit_state) { for f in functions { @@ -133,7 +133,7 @@ impl EventDispatcher { pub fn register_event_handler(&mut self, event: EventKind, f: F) where - F: Fn(&Event, &PairStatus) -> Fut, + F: Fn(&Event, &PriceManager) -> Fut, Fut: Future + Send, { match event { @@ -153,7 +153,7 @@ impl EventDispatcher { state: PositionProfitState, f: F, ) where - F: Fn(&Position, &PairStatus) -> Fut, + F: Fn(&Position, &PriceManager) -> Fut, Fut: Future + Send, { match state { diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index 22ce0de..eddc722 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -1,11 +1,13 @@ +#![feature(drain_filter)] + +use std::thread::JoinHandle; + use tokio::time::{delay_for, Duration}; use crate::bot::BfxBot; -use crate::connectors::BitfinexConnector; +use crate::connectors::{BitfinexConnector, ExchangeKind}; use crate::currency::{Symbol, SymbolPair}; use crate::events::SignalKind; -use crate::strategy::TrailingStop; -use std::thread::JoinHandle; mod bot; mod connectors; @@ -23,16 +25,20 @@ pub type BoxError = Box; async fn main() -> Result<(), BoxError> { let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2"; let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli"; - let bfx = BitfinexConnector::new(test_api_key, test_api_secret) - .with_affiliate_code(Some("XPebOgHxA".into())); + let affiliate_code = "XPebOgHxA"; + + let bitfinex = ExchangeKind::Bitfinex { + api_key: test_api_key.into(), + api_secret: test_api_secret.into(), + affiliate_code: Some(affiliate_code.into()), + }; let mut bot = BfxBot::new( - bfx, + vec![bitfinex], vec![Symbol::TESTBTC], Symbol::TESTUSD, - Duration::new(20, 0), - ) - .with_strategy(Box::new(TrailingStop::new())); + Duration::new(1, 0), + ); Ok(bot.start_loop().await?) } diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index c896632..8aa16f2 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -1,21 +1,58 @@ +use std::collections::{HashMap, VecDeque}; + use crate::connectors::{Client, Connector}; use crate::events::Event; use crate::models::{Order, Position}; +use crate::strategy::PositionStrategy; use crate::ticker::Ticker; -use std::collections::{HashMap, VecDeque}; -struct EventManager { +pub struct EventManager { events: Vec, } -struct PositionManager { +pub struct PositionManager { queue: VecDeque, open_positions: Vec, client: Client, + strategy: Option>, } -struct OrderManager { +impl PositionManager { + pub fn new(client: Client) -> Self { + PositionManager { + queue: VecDeque::new(), + open_positions: Vec::new(), + client, + strategy: None, + } + } + + pub fn with_strategy(mut self, strategy: Box) -> Self { + self.strategy = Some(strategy); + self + } + + pub fn update(&self) -> Option> { + unimplemented!() + } +} + +pub struct OrderManager { queue: VecDeque, open_orders: Vec, client: Client, } + +impl OrderManager { + pub fn new(client: Client) -> Self { + OrderManager { + queue: VecDeque::new(), + open_orders: Vec::new(), + client, + } + } + + pub fn update(&self) -> Option> { + unimplemented!() + } +} diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index 674b1bc..f769bc6 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -1,5 +1,28 @@ use crate::currency::SymbolPair; +/*************** +* Prices +***************/ + +#[derive(Copy, Clone, Debug)] +pub struct PriceTicker { + pub bid: f64, + pub bid_size: f64, + pub ask: f64, + pub ask_size: f64, + pub daily_change: f64, + pub daily_change_perc: f64, + pub last_price: f64, + pub volume: f64, + pub high: f64, + pub low: f64, +} + +/*************** +* Orders +***************/ + +#[derive(Clone, Debug)] pub struct Order { pub id: i64, pub group_id: Option, @@ -22,6 +45,7 @@ pub struct Order { pub placed_id: Option, } +#[derive(Copy, Clone, Debug)] pub enum OrderKind { Limit, ExchangeLimit, @@ -38,6 +62,10 @@ pub enum OrderKind { ExchangeIoc, } +/*************** +* Positions +***************/ + #[derive(Clone, Debug)] pub struct Position { pair: SymbolPair, diff --git a/rustybot/src/pairs.rs b/rustybot/src/pairs.rs index 479efe5..5ca22d6 100644 --- a/rustybot/src/pairs.rs +++ b/rustybot/src/pairs.rs @@ -1,117 +1,145 @@ use std::collections::HashMap; +// use crate::strategy::PositionStrategy; +use crate::connectors::Client; use crate::currency::SymbolPair; use crate::events::{Event, EventDispatcher, SignalKind}; -use crate::models::{Order, Position}; -use crate::strategy::PositionStrategy; +use crate::models::{Order, Position, PriceTicker}; +use crate::BoxError; -pub struct PairStatus<'a> { +#[derive(Clone, Debug)] +pub struct PriceManager { pair: SymbolPair, - dispatcher: EventDispatcher, - prices: HashMap, - events: Vec, - orders: HashMap>, - positions: HashMap>, - current_tick: u64, - strategy: Option>, - signals: HashMap, + prices: Vec, + client: Client, } -impl<'a> PairStatus<'a> { +#[derive(Clone, Debug)] +pub struct PriceEntry { + tick: u64, + pair: SymbolPair, + price: PriceTicker, + events: Option>, + signals: Option>, +} + +impl PriceEntry { + pub fn tick(&self) -> u64 { + self.tick + } + pub fn pair(&self) -> &SymbolPair { + &self.pair + } + pub fn price(&self) -> PriceTicker { + self.price + } + pub fn events(&self) -> &Option> { + &self.events + } + pub fn signals(&self) -> &Option> { + &self.signals + } +} + +impl PriceEntry { pub fn new( + tick: u64, + price: PriceTicker, pair: SymbolPair, - current_tick: u64, - strategy: Option>, + events: Option>, + signals: Option>, ) -> Self { - PairStatus { + PriceEntry { + tick, pair, - dispatcher: EventDispatcher::new(), - prices: HashMap::new(), - events: Vec::new(), - positions: HashMap::new(), - orders: HashMap::new(), - signals: HashMap::new(), - current_tick, - strategy, + price, + events, + signals, + } + } +} + +impl PriceManager { + pub fn new(pair: SymbolPair, client: Client) -> Self { + PriceManager { + pair, + prices: Vec::new(), + client, } } - pub fn dispatcher(&self) -> &EventDispatcher { - &self.dispatcher - } - pub fn dispatcher_mut(&mut self) -> &mut EventDispatcher { - &mut self.dispatcher + pub fn add_entry(&mut self, entry: PriceEntry) { + self.prices.push(entry); } - pub fn add_position(&mut self, position: Position) { - let (new_position, events, signals) = { - match &self.strategy { - Some(strategy) => strategy.on_new_tick(&position, &self), - None => (position, vec![], vec![]), - } - }; + pub async fn update(&mut self, tick: u64) -> Result { + let current_prices = self.client.current_prices(&self.pair).await?.into(); - self.positions - .entry(self.current_tick) - .or_default() - .push(new_position.clone()); - - // calling position state callbacks - self.dispatcher - .call_position_state_handlers(&new_position, &self); - - // adding events and calling callbacks - for e in events { - self.add_event(e); - } - - // adding signals to current tick vector - for s in signals { - self.add_signal(s); - } - - println!( - "EVENTS: {:?} | SIGNALS: {:?} | POSITION: {:?}", - self.events, self.signals, new_position - ); + Ok(PriceEntry::new( + tick, + current_prices, + self.pair.clone(), + None, + None, + )) } - fn add_event(&mut self, event: Event) { - self.events.push(event); + // pub fn add_position(&mut self, position: Position) { + // let (new_position, events, signals) = { + // match &self.strategy { + // Some(strategy) => strategy.on_new_tick(&position, &self), + // None => (position, vec![], vec![]), + // } + // }; + // + // self.positions + // .entry(self.current_tick) + // .or_default() + // .push(new_position.clone()); + // + // // calling position state callbacks + // self.dispatcher + // .call_position_state_handlers(&new_position, &self); + // + // // adding events and calling callbacks + // for e in events { + // self.add_event(e); + // } + // + // // adding signals to current tick vector + // for s in signals { + // self.add_signal(s); + // } + // } - self.dispatcher.call_event_handlers(&event, &self); - } - - fn add_signal(&mut self, signal: SignalKind) { - self.signals.insert(self.current_tick(), signal); - } - - pub fn current_tick(&self) -> u64 { - self.current_tick - } - - pub fn set_strategy(&mut self, strategy: Box) { - self.strategy = Some(strategy); - } + // fn add_event(&mut self, event: Event) { + // self.events.push(event); + // + // self.dispatcher.call_event_handlers(&event, &self); + // } + // + // fn add_signal(&mut self, signal: SignalKind) { + // self.signals.insert(self.current_tick(), signal); + // } pub fn pair(&self) -> &SymbolPair { &self.pair } - pub fn position_previous_tick(&self, id: u64, tick: Option) -> Option<&Position> { - let tick = match tick { - Some(tick) => { - if tick < 1 { - 1 - } else { - tick - } - } - None => self.current_tick() - 1, - }; - - self.positions - .get(&tick) - .and_then(|x| x.iter().find(|x| x.position_id() == id)) - } + // pub fn position_previous_tick(&self, id: u64, tick: Option) -> Option<&Position> { + // let tick = match tick { + // Some(tick) => { + // if tick < 1 { + // 1 + // } else { + // tick + // } + // } + // None => self.current_tick() - 1, + // }; + // + // self.positions + // .get(&tick) + // .and_then(|x| x.iter().find(|x| x.position_id() == id)) + // } } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 658dcb7..ed9589c 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -2,121 +2,121 @@ use std::collections::HashMap; use crate::events::{Event, EventKind, EventMetadata, SignalKind}; use crate::models::{Position, PositionProfitState}; -use crate::pairs::PairStatus; +use crate::pairs::PriceManager; use dyn_clone::DynClone; pub trait PositionStrategy: DynClone { fn on_new_tick( &self, position: &Position, - status: &PairStatus, + status: &PriceManager, ) -> (Position, Vec, Vec); } -#[derive(Clone, Debug)] -pub struct TrailingStop { - stop_percentages: HashMap, -} - -impl TrailingStop { - const BREAK_EVEN_PERC: f64 = 0.2; - const MIN_PROFIT_PERC: f64 = TrailingStop::BREAK_EVEN_PERC + 0.3; - const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 2.5; - const MAX_LOSS_PERC: f64 = -1.7; - - const TAKER_FEE: f64 = 0.2; - - pub fn new() -> Self { - TrailingStop { - stop_percentages: HashMap::new(), - } - } - - fn net_pl_percentage(pl: f64, fee: f64) -> f64 { - pl - fee - } -} - -impl PositionStrategy for TrailingStop { - fn on_new_tick( - &self, - position: &Position, - status: &PairStatus, - ) -> (Position, Vec, Vec) { - let mut signals = vec![]; - let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE); - let events = vec![]; - - let state = { - if pl_perc > TrailingStop::GOOD_PROFIT_PERC { - PositionProfitState::Profit - } else if TrailingStop::MIN_PROFIT_PERC <= pl_perc - && pl_perc < TrailingStop::GOOD_PROFIT_PERC - { - PositionProfitState::MinimumProfit - } else if 0.0 <= pl_perc && pl_perc < TrailingStop::MIN_PROFIT_PERC { - PositionProfitState::BreakEven - } else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 { - PositionProfitState::Loss - } else { - signals.push(SignalKind::ClosePosition { - position_id: position.position_id(), - }); - PositionProfitState::Critical - } - }; - - let opt_pre_pw = status.position_previous_tick(position.position_id(), None); - let event_metadata = EventMetadata::new(Some(position.position_id()), None); - let new_position = position.clone().with_profit_state(Some(state)); - - match opt_pre_pw { - Some(prev) => { - if prev.profit_state() == Some(state) { - return (new_position, events, signals); - } - } - None => return (new_position, events, signals), - }; - - let events = { - let mut events = vec![]; - - if state == PositionProfitState::Profit { - events.push(Event::new( - EventKind::ReachedGoodProfit, - status.current_tick(), - Some(event_metadata), - )); - } else if state == PositionProfitState::MinimumProfit { - events.push(Event::new( - EventKind::ReachedMinProfit, - status.current_tick(), - Some(event_metadata), - )); - } else if state == PositionProfitState::BreakEven { - events.push(Event::new( - EventKind::ReachedBreakEven, - status.current_tick(), - Some(event_metadata), - )); - } else if state == PositionProfitState::Loss { - events.push(Event::new( - EventKind::ReachedLoss, - status.current_tick(), - Some(event_metadata), - )); - } else { - events.push(Event::new( - EventKind::ReachedMaxLoss, - status.current_tick(), - Some(event_metadata), - )); - } - - events - }; - - return (new_position, events, signals); - } -} +// #[derive(Clone, Debug)] +// pub struct TrailingStop { +// stop_percentages: HashMap, +// } +// +// impl TrailingStop { +// const BREAK_EVEN_PERC: f64 = 0.2; +// const MIN_PROFIT_PERC: f64 = TrailingStop::BREAK_EVEN_PERC + 0.3; +// const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 2.5; +// const MAX_LOSS_PERC: f64 = -1.7; +// +// const TAKER_FEE: f64 = 0.2; +// +// pub fn new() -> Self { +// TrailingStop { +// stop_percentages: HashMap::new(), +// } +// } +// +// fn net_pl_percentage(pl: f64, fee: f64) -> f64 { +// pl - fee +// } +// } +// +// impl PositionStrategy for TrailingStop { +// fn on_new_tick( +// &self, +// position: &Position, +// status: &PairStatus, +// ) -> (Position, Vec, Vec) { +// let mut signals = vec![]; +// let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE); +// let events = vec![]; +// +// let state = { +// if pl_perc > TrailingStop::GOOD_PROFIT_PERC { +// PositionProfitState::Profit +// } else if TrailingStop::MIN_PROFIT_PERC <= pl_perc +// && pl_perc < TrailingStop::GOOD_PROFIT_PERC +// { +// PositionProfitState::MinimumProfit +// } else if 0.0 <= pl_perc && pl_perc < TrailingStop::MIN_PROFIT_PERC { +// PositionProfitState::BreakEven +// } else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 { +// PositionProfitState::Loss +// } else { +// signals.push(SignalKind::ClosePosition { +// position_id: position.position_id(), +// }); +// PositionProfitState::Critical +// } +// }; +// +// let opt_pre_pw = status.position_previous_tick(position.position_id(), None); +// let event_metadata = EventMetadata::new(Some(position.position_id()), None); +// let new_position = position.clone().with_profit_state(Some(state)); +// +// match opt_pre_pw { +// Some(prev) => { +// if prev.profit_state() == Some(state) { +// return (new_position, events, signals); +// } +// } +// None => return (new_position, events, signals), +// }; +// +// let events = { +// let mut events = vec![]; +// +// if state == PositionProfitState::Profit { +// events.push(Event::new( +// EventKind::ReachedGoodProfit, +// status.current_tick(), +// Some(event_metadata), +// )); +// } else if state == PositionProfitState::MinimumProfit { +// events.push(Event::new( +// EventKind::ReachedMinProfit, +// status.current_tick(), +// Some(event_metadata), +// )); +// } else if state == PositionProfitState::BreakEven { +// events.push(Event::new( +// EventKind::ReachedBreakEven, +// status.current_tick(), +// Some(event_metadata), +// )); +// } else if state == PositionProfitState::Loss { +// events.push(Event::new( +// EventKind::ReachedLoss, +// status.current_tick(), +// Some(event_metadata), +// )); +// } else { +// events.push(Event::new( +// EventKind::ReachedMaxLoss, +// status.current_tick(), +// Some(event_metadata), +// )); +// } +// +// events +// }; +// +// return (new_position, events, signals); +// } +// } -- 2.47.2 From c87da2bb6a7b5e6dba8af08d9cbb66a10375d892 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Thu, 14 Jan 2021 12:43:01 +0000 Subject: [PATCH 037/127] removed explicit lifetime --- rustybot/src/bot.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index e832c3e..899b543 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -67,7 +67,7 @@ impl BfxBot { } } - async fn update<'a>(&'a mut self) { + async fn update(&mut self) { delay_for(self.ticker.duration()).await; self.ticker.inc(); -- 2.47.2 From 23c2d5864785ba7e1671547838fa873b4791ac81 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Thu, 14 Jan 2021 12:46:35 +0000 Subject: [PATCH 038/127] correct loop (not ignoring first tick). grouped together managers update --- rustybot/src/bot.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index 899b543..6743af7 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -62,16 +62,26 @@ impl BfxBot { } pub async fn start_loop(&mut self) -> Result<(), BoxError> { + self.update_managers().await?; + loop { self.update().await; } } - async fn update(&mut self) { + async fn update(&mut self) -> Result<(), BoxError> { delay_for(self.ticker.duration()).await; self.ticker.inc(); - self.update_price_managers().await.unwrap(); + self.update_managers().await?; + + Ok(()) + } + + async fn update_managers(&mut self) -> Result<(), BoxError> { + self.update_price_managers().await?; + + Ok(()) } async fn update_price_managers(&mut self) -> Result>, BoxError> { -- 2.47.2 From dcc12934550b6e6ee68ba27c7c3c646395ee749c Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Thu, 14 Jan 2021 12:53:54 +0000 Subject: [PATCH 039/127] moved price manager from pairs into managers --- rustybot/src/bot.rs | 5 +- rustybot/src/events.rs | 2 +- rustybot/src/managers.rs | 141 ++++++++++++++++++++++++++++++++++++- rustybot/src/pairs.rs | 145 --------------------------------------- rustybot/src/strategy.rs | 2 +- 5 files changed, 143 insertions(+), 152 deletions(-) delete mode 100644 rustybot/src/pairs.rs diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index 6743af7..8e92842 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -5,17 +5,16 @@ use bitfinex::api::Bitfinex; use bitfinex::positions::Position; use bitfinex::ticker::TradingPairTicker; use futures_util::stream::FuturesUnordered; +use tokio::stream::StreamExt; use tokio::time::delay_for; use crate::connectors::{Client, Connector, ExchangeKind}; use crate::currency::{Symbol, SymbolPair}; use crate::events::{Event, EventKind}; -use crate::managers::{OrderManager, PositionManager}; -use crate::pairs::PriceManager; +use crate::managers::{OrderManager, PositionManager, PriceManager}; use crate::strategy::PositionStrategy; use crate::ticker::Ticker; use crate::BoxError; -use tokio::stream::StreamExt; pub struct BfxBot { ticker: Ticker, diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 1fdfa14..6bff8cc 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -5,8 +5,8 @@ use tokio::stream::StreamExt; use tokio::task::JoinHandle; use crate::bot::BfxBot; +use crate::managers::PriceManager; use crate::models::{Position, PositionProfitState}; -use crate::pairs::PriceManager; use crate::BoxError; #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 8aa16f2..e9a2721 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -1,15 +1,152 @@ use std::collections::{HashMap, VecDeque}; use crate::connectors::{Client, Connector}; -use crate::events::Event; -use crate::models::{Order, Position}; +use crate::currency::SymbolPair; +use crate::events::{Event, EventDispatcher, SignalKind}; +use crate::models::{Order, Position, PriceTicker}; use crate::strategy::PositionStrategy; use crate::ticker::Ticker; +use crate::BoxError; pub struct EventManager { events: Vec, } +#[derive(Clone, Debug)] +pub struct PriceManager { + pair: SymbolPair, + prices: Vec, + client: Client, +} + +impl PriceManager { + pub fn new(pair: SymbolPair, client: Client) -> Self { + PriceManager { + pair, + prices: Vec::new(), + client, + } + } + + pub fn add_entry(&mut self, entry: PriceEntry) { + self.prices.push(entry); + } + + pub async fn update(&mut self, tick: u64) -> Result { + let current_prices = self.client.current_prices(&self.pair).await?.into(); + + Ok(PriceEntry::new( + tick, + current_prices, + self.pair.clone(), + None, + None, + )) + } + + // pub fn add_position(&mut self, position: Position) { + // let (new_position, events, signals) = { + // match &self.strategy { + // Some(strategy) => strategy.on_new_tick(&position, &self), + // None => (position, vec![], vec![]), + // } + // }; + // + // self.positions + // .entry(self.current_tick) + // .or_default() + // .push(new_position.clone()); + // + // // calling position state callbacks + // self.dispatcher + // .call_position_state_handlers(&new_position, &self); + // + // // adding events and calling callbacks + // for e in events { + // self.add_event(e); + // } + // + // // adding signals to current tick vector + // for s in signals { + // self.add_signal(s); + // } + // } + + // fn add_event(&mut self, event: Event) { + // self.events.push(event); + // + // self.dispatcher.call_event_handlers(&event, &self); + // } + // + // fn add_signal(&mut self, signal: SignalKind) { + // self.signals.insert(self.current_tick(), signal); + // } + + pub fn pair(&self) -> &SymbolPair { + &self.pair + } + + // pub fn position_previous_tick(&self, id: u64, tick: Option) -> Option<&Position> { + // let tick = match tick { + // Some(tick) => { + // if tick < 1 { + // 1 + // } else { + // tick + // } + // } + // None => self.current_tick() - 1, + // }; + // + // self.positions + // .get(&tick) + // .and_then(|x| x.iter().find(|x| x.position_id() == id)) + // } +} + +#[derive(Clone, Debug)] +pub struct PriceEntry { + tick: u64, + pair: SymbolPair, + price: PriceTicker, + events: Option>, + signals: Option>, +} + +impl PriceEntry { + pub fn new( + tick: u64, + price: PriceTicker, + pair: SymbolPair, + events: Option>, + signals: Option>, + ) -> Self { + PriceEntry { + tick, + pair, + price, + events, + signals, + } + } + + pub fn tick(&self) -> u64 { + self.tick + } + pub fn pair(&self) -> &SymbolPair { + &self.pair + } + pub fn price(&self) -> PriceTicker { + self.price + } + pub fn events(&self) -> &Option> { + &self.events + } + pub fn signals(&self) -> &Option> { + &self.signals + } +} + pub struct PositionManager { queue: VecDeque, open_positions: Vec, diff --git a/rustybot/src/pairs.rs b/rustybot/src/pairs.rs deleted file mode 100644 index 5ca22d6..0000000 --- a/rustybot/src/pairs.rs +++ /dev/null @@ -1,145 +0,0 @@ -use std::collections::HashMap; - -// use crate::strategy::PositionStrategy; -use crate::connectors::Client; -use crate::currency::SymbolPair; -use crate::events::{Event, EventDispatcher, SignalKind}; -use crate::models::{Order, Position, PriceTicker}; -use crate::BoxError; - -#[derive(Clone, Debug)] -pub struct PriceManager { - pair: SymbolPair, - prices: Vec, - client: Client, -} - -#[derive(Clone, Debug)] -pub struct PriceEntry { - tick: u64, - pair: SymbolPair, - price: PriceTicker, - events: Option>, - signals: Option>, -} - -impl PriceEntry { - pub fn tick(&self) -> u64 { - self.tick - } - pub fn pair(&self) -> &SymbolPair { - &self.pair - } - pub fn price(&self) -> PriceTicker { - self.price - } - pub fn events(&self) -> &Option> { - &self.events - } - pub fn signals(&self) -> &Option> { - &self.signals - } -} - -impl PriceEntry { - pub fn new( - tick: u64, - price: PriceTicker, - pair: SymbolPair, - events: Option>, - signals: Option>, - ) -> Self { - PriceEntry { - tick, - pair, - price, - events, - signals, - } - } -} - -impl PriceManager { - pub fn new(pair: SymbolPair, client: Client) -> Self { - PriceManager { - pair, - prices: Vec::new(), - client, - } - } - - pub fn add_entry(&mut self, entry: PriceEntry) { - self.prices.push(entry); - } - - pub async fn update(&mut self, tick: u64) -> Result { - let current_prices = self.client.current_prices(&self.pair).await?.into(); - - Ok(PriceEntry::new( - tick, - current_prices, - self.pair.clone(), - None, - None, - )) - } - - // pub fn add_position(&mut self, position: Position) { - // let (new_position, events, signals) = { - // match &self.strategy { - // Some(strategy) => strategy.on_new_tick(&position, &self), - // None => (position, vec![], vec![]), - // } - // }; - // - // self.positions - // .entry(self.current_tick) - // .or_default() - // .push(new_position.clone()); - // - // // calling position state callbacks - // self.dispatcher - // .call_position_state_handlers(&new_position, &self); - // - // // adding events and calling callbacks - // for e in events { - // self.add_event(e); - // } - // - // // adding signals to current tick vector - // for s in signals { - // self.add_signal(s); - // } - // } - - // fn add_event(&mut self, event: Event) { - // self.events.push(event); - // - // self.dispatcher.call_event_handlers(&event, &self); - // } - // - // fn add_signal(&mut self, signal: SignalKind) { - // self.signals.insert(self.current_tick(), signal); - // } - - pub fn pair(&self) -> &SymbolPair { - &self.pair - } - - // pub fn position_previous_tick(&self, id: u64, tick: Option) -> Option<&Position> { - // let tick = match tick { - // Some(tick) => { - // if tick < 1 { - // 1 - // } else { - // tick - // } - // } - // None => self.current_tick() - 1, - // }; - // - // self.positions - // .get(&tick) - // .and_then(|x| x.iter().find(|x| x.position_id() == id)) - // } -} diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index ed9589c..23ed3cf 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; use crate::events::{Event, EventKind, EventMetadata, SignalKind}; +use crate::managers::PriceManager; use crate::models::{Position, PositionProfitState}; -use crate::pairs::PriceManager; use dyn_clone::DynClone; pub trait PositionStrategy: DynClone { -- 2.47.2 From 3eca8aef2d0b1eccfc5b0364e16279c47efde509 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Thu, 14 Jan 2021 12:57:16 +0000 Subject: [PATCH 040/127] removed mod pair from main --- rustybot/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index eddc722..de05953 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -15,7 +15,6 @@ mod currency; mod events; mod managers; mod models; -mod pairs; mod strategy; mod ticker; -- 2.47.2 From 2c151ae6c18907a05f766e9e60d2ec6f20462381 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Thu, 14 Jan 2021 18:36:56 +0000 Subject: [PATCH 041/127] position manager working --- rustybot/src/bot.rs | 31 +++++++++++++------ rustybot/src/connectors.rs | 19 +++++++++--- rustybot/src/managers.rs | 63 ++++++++++++++++++++++++++++++++------ rustybot/src/models.rs | 2 +- rustybot/src/strategy.rs | 11 +++++-- 5 files changed, 99 insertions(+), 27 deletions(-) diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index 8e92842..f023d97 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -33,21 +33,21 @@ impl BfxBot { tick_duration: Duration, ) -> Self { let clients: Vec<_> = exchanges.iter().map(|x| Client::new(x)).collect(); + let pairs: Vec<_> = trading_symbols + .iter() + .map(|x| SymbolPair::new(quote.clone(), x.clone())) + .collect(); let mut pos_managers = Vec::new(); let mut order_managers = Vec::new(); let mut pair_statuses = Vec::new(); for c in clients { - pos_managers.push(PositionManager::new(c.clone())); - order_managers.push(OrderManager::new(c.clone())); - pair_statuses.extend( - trading_symbols - .iter() - .map(|x| SymbolPair::new(quote.clone(), x.clone())) - .map(|x| PriceManager::new(x, c.clone())) - .collect::>(), - ) + for p in &pairs { + pos_managers.push(PositionManager::new(p.clone(), c.clone())); + order_managers.push(OrderManager::new(p.clone(), c.clone())); + pair_statuses.push(PriceManager::new(p.clone(), c.clone())); + } } BfxBot { @@ -79,10 +79,23 @@ impl BfxBot { async fn update_managers(&mut self) -> Result<(), BoxError> { self.update_price_managers().await?; + self.update_position_managers().await?; Ok(()) } + async fn update_position_managers(&mut self) -> Result>, BoxError> { + for mgr in &mut self.pos_managers { + let tick = self.ticker.current_tick(); + + mgr.update(tick).await?; + + println!("{:?}", mgr); + } + + Ok(None) + } + async fn update_price_managers(&mut self) -> Result>, BoxError> { let futures: Vec<_> = self .price_managers diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index bbe5725..c7c99df 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -45,7 +45,10 @@ impl Client { } } - pub async fn active_positions(&self, pair: &SymbolPair) -> Result, BoxError> { + pub async fn active_positions( + &self, + pair: &SymbolPair, + ) -> Result>, BoxError> { self.inner.active_positions(pair).await } @@ -70,7 +73,7 @@ impl Client { #[async_trait] pub trait Connector: Send + Sync { - async fn active_positions(&self, pair: &SymbolPair) -> Result, BoxError>; + async fn active_positions(&self, pair: &SymbolPair) -> Result>, BoxError>; async fn current_prices(&self, pair: &SymbolPair) -> Result; async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError>; async fn submit_order( @@ -123,14 +126,20 @@ impl BitfinexConnector { #[async_trait] impl Connector for BitfinexConnector { - async fn active_positions(&self, pair: &SymbolPair) -> Result, BoxError> { + async fn active_positions(&self, pair: &SymbolPair) -> Result>, BoxError> { let active_positions = self.bfx.positions.active_positions().await?; - Ok(active_positions + let positions: Vec<_> = active_positions .into_iter() .filter_map(|x| x.try_into().ok()) .filter(|x: &Position| x.pair() == pair) - .collect()) + .collect(); + + if positions.is_empty() { + Ok(None) + } else { + Ok(Some(positions)) + } } async fn current_prices(&self, pair: &SymbolPair) -> Result { diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index e9a2721..4a0d0c9 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -147,18 +147,21 @@ impl PriceEntry { } } +#[derive(Debug)] pub struct PositionManager { - queue: VecDeque, - open_positions: Vec, + pair: SymbolPair, + positions_history: HashMap, + active_position: Option, client: Client, strategy: Option>, } impl PositionManager { - pub fn new(client: Client) -> Self { + pub fn new(pair: SymbolPair, client: Client) -> Self { PositionManager { - queue: VecDeque::new(), - open_positions: Vec::new(), + pair, + positions_history: HashMap::new(), + active_position: None, client, strategy: None, } @@ -169,21 +172,61 @@ impl PositionManager { self } - pub fn update(&self) -> Option> { - unimplemented!() + pub async fn update(&mut self, tick: u64) -> Result>, BoxError> { + let mut opt_active_positions = self.client.active_positions(&self.pair).await?; + let mut events = vec![]; + + if opt_active_positions.is_none() { + return Ok(None); + } + + // we assume there is only ONE active position per pair + match opt_active_positions + .unwrap() + .into_iter() + .filter(|x| x.pair() == &self.pair) + .next() + { + Some(position) => { + // applying strategy to position + let active_position = { + match &self.strategy { + Some(strategy) => { + let (pos, strategy_events, _) = strategy.on_new_tick(&position, &self); + + events.extend(strategy_events); + pos + } + None => position, + } + }; + + self.positions_history.insert(tick, active_position.clone()); + self.active_position = Some(active_position); + } + None => { + self.active_position = None; + } + } + + if events.is_empty() { + Ok(None) + } else { + Ok(Some(events)) + } } } pub struct OrderManager { - queue: VecDeque, + pair: SymbolPair, open_orders: Vec, client: Client, } impl OrderManager { - pub fn new(client: Client) -> Self { + pub fn new(pair: SymbolPair, client: Client) -> Self { OrderManager { - queue: VecDeque::new(), + pair, open_orders: Vec::new(), client, } diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index f769bc6..e0423ed 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -66,7 +66,7 @@ pub enum OrderKind { * Positions ***************/ -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct Position { pair: SymbolPair, state: PositionState, diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 23ed3cf..11f569c 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -1,18 +1,25 @@ use std::collections::HashMap; use crate::events::{Event, EventKind, EventMetadata, SignalKind}; -use crate::managers::PriceManager; +use crate::managers::{PositionManager, PriceManager}; use crate::models::{Position, PositionProfitState}; use dyn_clone::DynClone; +use std::fmt::{Debug, Formatter}; pub trait PositionStrategy: DynClone { fn on_new_tick( &self, position: &Position, - status: &PriceManager, + manager: &PositionManager, ) -> (Position, Vec, Vec); } +impl Debug for dyn PositionStrategy { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "PositionStrategy") + } +} + // #[derive(Clone, Debug)] // pub struct TrailingStop { // stop_percentages: HashMap, -- 2.47.2 From b9564dc8123b421d2b96c955f4cf590912ba06c2 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Thu, 14 Jan 2021 18:56:31 +0000 Subject: [PATCH 042/127] warnings cleanup and logging --- rustybot/Cargo.lock | 34 +++++++++++++++++++++++++++++++++ rustybot/Cargo.toml | 4 +++- rustybot/src/bot.rs | 30 ++++++++++++++--------------- rustybot/src/connectors.rs | 6 +++--- rustybot/src/currency.rs | 2 +- rustybot/src/events.rs | 5 +---- rustybot/src/main.rs | 39 ++++++++++++++++++++++++++++++++------ rustybot/src/managers.rs | 9 ++++----- rustybot/src/strategy.rs | 12 ++++++------ 9 files changed, 100 insertions(+), 41 deletions(-) diff --git a/rustybot/Cargo.lock b/rustybot/Cargo.lock index 0b385b8..5025cfb 100644 --- a/rustybot/Cargo.lock +++ b/rustybot/Cargo.lock @@ -35,6 +35,17 @@ dependencies = [ "syn", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + [[package]] name = "autocfg" version = "1.0.1" @@ -198,6 +209,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "colored" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59" +dependencies = [ + "atty", + "lazy_static", + "winapi 0.3.9", +] + [[package]] name = "core-foundation" version = "0.9.1" @@ -269,6 +291,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fern" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9a4820f0ccc8a7afd67c39a0f1a0f4b07ca1725164271a64939d7aeb9af065" +dependencies = [ + "colored", + "log 0.4.11", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1097,7 +1129,9 @@ dependencies = [ "async-trait", "bitfinex", "dyn-clone", + "fern", "futures-util", + "log 0.4.11", "regex", "tokio 0.2.24", "tokio-tungstenite", diff --git a/rustybot/Cargo.toml b/rustybot/Cargo.toml index 3985630..e328703 100644 --- a/rustybot/Cargo.toml +++ b/rustybot/Cargo.toml @@ -13,4 +13,6 @@ tokio-tungstenite = "*" futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] } async-trait = "0.1" regex = "1" -dyn-clone = "1" \ No newline at end of file +dyn-clone = "1" +log = "0.4" +fern = {version = "0.6", features = ["colored"]} \ No newline at end of file diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index f023d97..bea705a 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -1,18 +1,12 @@ use core::time::Duration; -use std::collections::HashMap; -use bitfinex::api::Bitfinex; -use bitfinex::positions::Position; -use bitfinex::ticker::TradingPairTicker; -use futures_util::stream::FuturesUnordered; -use tokio::stream::StreamExt; +use log::{error, info}; use tokio::time::delay_for; -use crate::connectors::{Client, Connector, ExchangeKind}; +use crate::connectors::{Client, ExchangeKind}; use crate::currency::{Symbol, SymbolPair}; -use crate::events::{Event, EventKind}; +use crate::events::Event; use crate::managers::{OrderManager, PositionManager, PriceManager}; -use crate::strategy::PositionStrategy; use crate::ticker::Ticker; use crate::BoxError; @@ -61,10 +55,16 @@ impl BfxBot { } pub async fn start_loop(&mut self) -> Result<(), BoxError> { - self.update_managers().await?; + if let Err(e) = self.update_managers().await { + error!("Error while starting managers: {}", e); + } loop { - self.update().await; + info!("Current tick: {}", self.ticker.current_tick()); + + if let Err(e) = self.update().await { + error!("Error in main bot loop: {}", e); + } } } @@ -72,7 +72,9 @@ impl BfxBot { delay_for(self.ticker.duration()).await; self.ticker.inc(); - self.update_managers().await?; + if let Err(e) = self.update_managers().await { + error!("Error while updating managers: {}", e); + } Ok(()) } @@ -89,8 +91,6 @@ impl BfxBot { let tick = self.ticker.current_tick(); mgr.update(tick).await?; - - println!("{:?}", mgr); } Ok(None) @@ -117,7 +117,7 @@ impl BfxBot { price_entries.push(f.await??); } - for mut manager in &mut self.price_managers { + for manager in &mut self.price_managers { let prices: Vec<_> = price_entries .drain_filter(|x| x.pair() == manager.pair()) .collect(); diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index c7c99df..8f9dd49 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -1,4 +1,5 @@ -use std::convert::{TryFrom, TryInto}; +use std::convert::TryInto; +use std::fmt::{Debug, Formatter}; use std::sync::Arc; use async_trait::async_trait; @@ -6,10 +7,9 @@ use bitfinex::api::Bitfinex; use bitfinex::orders::{OrderForm, OrderMeta}; use bitfinex::ticker::TradingPairTicker; -use crate::currency::{Symbol, SymbolPair}; +use crate::currency::SymbolPair; use crate::models::{Order, OrderKind, Position, PositionState, PriceTicker}; use crate::BoxError; -use std::fmt::{Debug, Formatter}; #[derive(Eq, PartialEq, Hash, Clone, Debug)] pub enum ExchangeKind { diff --git a/rustybot/src/currency.rs b/rustybot/src/currency.rs index b5b0fd3..87f02be 100644 --- a/rustybot/src/currency.rs +++ b/rustybot/src/currency.rs @@ -1,7 +1,7 @@ use core::fmt; use std::borrow::Cow; use std::convert::TryFrom; -use std::fmt::{Display, Error, Formatter}; +use std::fmt::{Display, Formatter}; use regex::Regex; diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 6bff8cc..6229c3f 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -1,13 +1,10 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::future::Future; -use tokio::stream::StreamExt; use tokio::task::JoinHandle; -use crate::bot::BfxBot; use crate::managers::PriceManager; use crate::models::{Position, PositionProfitState}; -use crate::BoxError; #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub enum SignalKind { diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index de05953..41f24cd 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -1,13 +1,12 @@ #![feature(drain_filter)] -use std::thread::JoinHandle; - -use tokio::time::{delay_for, Duration}; +use fern::colors::{Color, ColoredLevelConfig}; +use log::LevelFilter::Info; +use tokio::time::Duration; use crate::bot::BfxBot; -use crate::connectors::{BitfinexConnector, ExchangeKind}; -use crate::currency::{Symbol, SymbolPair}; -use crate::events::SignalKind; +use crate::connectors::ExchangeKind; +use crate::currency::Symbol; mod bot; mod connectors; @@ -22,6 +21,8 @@ pub type BoxError = Box; #[tokio::main] async fn main() -> Result<(), BoxError> { + setup_logger()?; + let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2"; let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli"; let affiliate_code = "XPebOgHxA"; @@ -41,3 +42,29 @@ async fn main() -> Result<(), BoxError> { Ok(bot.start_loop().await?) } + +fn setup_logger() -> Result<(), fern::InitError> { + let colors = ColoredLevelConfig::new() + .info(Color::Green) + .error(Color::Red) + .trace(Color::Blue) + .debug(Color::Cyan) + .warn(Color::Yellow); + + fern::Dispatch::new() + .format(move |out, message, record| { + out.finish(format_args!( + "[{}][{}] {}", + record.target(), + colors.color(record.level()), + message + )) + }) + .level(Info) + .filter(|metadata| metadata.target().contains("rustybot")) + .chain(std::io::stdout()) + .chain(fern::log_file("rustico.log")?) + .apply()?; + + Ok(()) +} diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 4a0d0c9..9db42d6 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -1,11 +1,10 @@ -use std::collections::{HashMap, VecDeque}; +use std::collections::HashMap; -use crate::connectors::{Client, Connector}; +use crate::connectors::Client; use crate::currency::SymbolPair; -use crate::events::{Event, EventDispatcher, SignalKind}; +use crate::events::{Event, SignalKind}; use crate::models::{Order, Position, PriceTicker}; use crate::strategy::PositionStrategy; -use crate::ticker::Ticker; use crate::BoxError; pub struct EventManager { @@ -173,7 +172,7 @@ impl PositionManager { } pub async fn update(&mut self, tick: u64) -> Result>, BoxError> { - let mut opt_active_positions = self.client.active_positions(&self.pair).await?; + let opt_active_positions = self.client.active_positions(&self.pair).await?; let mut events = vec![]; if opt_active_positions.is_none() { diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 11f569c..a6fe086 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -1,11 +1,11 @@ -use std::collections::HashMap; - -use crate::events::{Event, EventKind, EventMetadata, SignalKind}; -use crate::managers::{PositionManager, PriceManager}; -use crate::models::{Position, PositionProfitState}; -use dyn_clone::DynClone; use std::fmt::{Debug, Formatter}; +use dyn_clone::DynClone; + +use crate::events::{Event, SignalKind}; +use crate::managers::PositionManager; +use crate::models::Position; + pub trait PositionStrategy: DynClone { fn on_new_tick( &self, -- 2.47.2 From c5b4aba54890ef426380d01d00ec995ae5aebd9b Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Thu, 14 Jan 2021 19:04:30 +0000 Subject: [PATCH 043/127] set debug as standard output level for logging --- rustybot/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index 41f24cd..57b3e00 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -1,7 +1,7 @@ #![feature(drain_filter)] use fern::colors::{Color, ColoredLevelConfig}; -use log::LevelFilter::Info; +use log::LevelFilter::Debug; use tokio::time::Duration; use crate::bot::BfxBot; @@ -60,7 +60,7 @@ fn setup_logger() -> Result<(), fern::InitError> { message )) }) - .level(Info) + .level(Debug) .filter(|metadata| metadata.target().contains("rustybot")) .chain(std::io::stdout()) .chain(fern::log_file("rustico.log")?) -- 2.47.2 From 2db59942eb337bcd21e90f2b669faf55441152e5 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Thu, 14 Jan 2021 19:20:58 +0000 Subject: [PATCH 044/127] position strategy working --- rustybot/src/bot.rs | 13 ++- rustybot/src/main.rs | 4 +- rustybot/src/managers.rs | 46 ++++---- rustybot/src/strategy.rs | 219 ++++++++++++++++++++------------------- 4 files changed, 153 insertions(+), 129 deletions(-) diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index bea705a..3d48ae5 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -1,12 +1,13 @@ use core::time::Duration; -use log::{error, info}; +use log::{debug, error, info}; use tokio::time::delay_for; use crate::connectors::{Client, ExchangeKind}; use crate::currency::{Symbol, SymbolPair}; use crate::events::Event; use crate::managers::{OrderManager, PositionManager, PriceManager}; +use crate::strategy::PositionStrategy; use crate::ticker::Ticker; use crate::BoxError; @@ -54,6 +55,16 @@ impl BfxBot { } } + pub fn with_position_strategy(mut self, strategy: Box) -> Self { + self.pos_managers = self + .pos_managers + .into_iter() + .map(|x| x.with_strategy(dyn_clone::clone_box(&*strategy))) + .collect(); + + self + } + pub async fn start_loop(&mut self) -> Result<(), BoxError> { if let Err(e) = self.update_managers().await { error!("Error while starting managers: {}", e); diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index 57b3e00..a280619 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -7,6 +7,7 @@ use tokio::time::Duration; use crate::bot::BfxBot; use crate::connectors::ExchangeKind; use crate::currency::Symbol; +use crate::strategy::TrailingStop; mod bot; mod connectors; @@ -38,7 +39,8 @@ async fn main() -> Result<(), BoxError> { vec![Symbol::TESTBTC], Symbol::TESTUSD, Duration::new(1, 0), - ); + ) + .with_position_strategy(Box::new(TrailingStop::new())); Ok(bot.start_loop().await?) } diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 9db42d6..4f28192 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -84,23 +84,6 @@ impl PriceManager { pub fn pair(&self) -> &SymbolPair { &self.pair } - - // pub fn position_previous_tick(&self, id: u64, tick: Option) -> Option<&Position> { - // let tick = match tick { - // Some(tick) => { - // if tick < 1 { - // 1 - // } else { - // tick - // } - // } - // None => self.current_tick() - 1, - // }; - // - // self.positions - // .get(&tick) - // .and_then(|x| x.iter().find(|x| x.position_id() == id)) - // } } #[derive(Clone, Debug)] @@ -148,6 +131,7 @@ impl PriceEntry { #[derive(Debug)] pub struct PositionManager { + current_tick: u64, pair: SymbolPair, positions_history: HashMap, active_position: Option, @@ -158,6 +142,7 @@ pub struct PositionManager { impl PositionManager { pub fn new(pair: SymbolPair, client: Client) -> Self { PositionManager { + current_tick: 0, pair, positions_history: HashMap::new(), active_position: None, @@ -171,10 +156,16 @@ impl PositionManager { self } + pub fn current_tick(&self) -> u64 { + self.current_tick + } + pub async fn update(&mut self, tick: u64) -> Result>, BoxError> { let opt_active_positions = self.client.active_positions(&self.pair).await?; let mut events = vec![]; + self.current_tick = tick; + if opt_active_positions.is_none() { return Ok(None); } @@ -200,7 +191,8 @@ impl PositionManager { } }; - self.positions_history.insert(tick, active_position.clone()); + self.positions_history + .insert(self.current_tick(), active_position.clone()); self.active_position = Some(active_position); } None => { @@ -214,6 +206,24 @@ impl PositionManager { Ok(Some(events)) } } + + pub fn position_previous_tick(&self, id: u64, tick: Option) -> Option<&Position> { + let tick = match tick { + Some(tick) => { + if tick < 1 { + 1 + } else { + tick + } + } + None => self.current_tick() - 1, + }; + + self.positions_history + .get(&tick) + .filter(|x| x.position_id() == id) + .and_then(|x| Some(x)) + } } pub struct OrderManager { diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index a6fe086..bdf1fa6 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -1,10 +1,11 @@ +use std::collections::HashMap; use std::fmt::{Debug, Formatter}; use dyn_clone::DynClone; -use crate::events::{Event, SignalKind}; +use crate::events::{Event, EventKind, EventMetadata, SignalKind}; use crate::managers::PositionManager; -use crate::models::Position; +use crate::models::{Position, PositionProfitState}; pub trait PositionStrategy: DynClone { fn on_new_tick( @@ -20,110 +21,110 @@ impl Debug for dyn PositionStrategy { } } -// #[derive(Clone, Debug)] -// pub struct TrailingStop { -// stop_percentages: HashMap, -// } -// -// impl TrailingStop { -// const BREAK_EVEN_PERC: f64 = 0.2; -// const MIN_PROFIT_PERC: f64 = TrailingStop::BREAK_EVEN_PERC + 0.3; -// const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 2.5; -// const MAX_LOSS_PERC: f64 = -1.7; -// -// const TAKER_FEE: f64 = 0.2; -// -// pub fn new() -> Self { -// TrailingStop { -// stop_percentages: HashMap::new(), -// } -// } -// -// fn net_pl_percentage(pl: f64, fee: f64) -> f64 { -// pl - fee -// } -// } -// -// impl PositionStrategy for TrailingStop { -// fn on_new_tick( -// &self, -// position: &Position, -// status: &PairStatus, -// ) -> (Position, Vec, Vec) { -// let mut signals = vec![]; -// let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE); -// let events = vec![]; -// -// let state = { -// if pl_perc > TrailingStop::GOOD_PROFIT_PERC { -// PositionProfitState::Profit -// } else if TrailingStop::MIN_PROFIT_PERC <= pl_perc -// && pl_perc < TrailingStop::GOOD_PROFIT_PERC -// { -// PositionProfitState::MinimumProfit -// } else if 0.0 <= pl_perc && pl_perc < TrailingStop::MIN_PROFIT_PERC { -// PositionProfitState::BreakEven -// } else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 { -// PositionProfitState::Loss -// } else { -// signals.push(SignalKind::ClosePosition { -// position_id: position.position_id(), -// }); -// PositionProfitState::Critical -// } -// }; -// -// let opt_pre_pw = status.position_previous_tick(position.position_id(), None); -// let event_metadata = EventMetadata::new(Some(position.position_id()), None); -// let new_position = position.clone().with_profit_state(Some(state)); -// -// match opt_pre_pw { -// Some(prev) => { -// if prev.profit_state() == Some(state) { -// return (new_position, events, signals); -// } -// } -// None => return (new_position, events, signals), -// }; -// -// let events = { -// let mut events = vec![]; -// -// if state == PositionProfitState::Profit { -// events.push(Event::new( -// EventKind::ReachedGoodProfit, -// status.current_tick(), -// Some(event_metadata), -// )); -// } else if state == PositionProfitState::MinimumProfit { -// events.push(Event::new( -// EventKind::ReachedMinProfit, -// status.current_tick(), -// Some(event_metadata), -// )); -// } else if state == PositionProfitState::BreakEven { -// events.push(Event::new( -// EventKind::ReachedBreakEven, -// status.current_tick(), -// Some(event_metadata), -// )); -// } else if state == PositionProfitState::Loss { -// events.push(Event::new( -// EventKind::ReachedLoss, -// status.current_tick(), -// Some(event_metadata), -// )); -// } else { -// events.push(Event::new( -// EventKind::ReachedMaxLoss, -// status.current_tick(), -// Some(event_metadata), -// )); -// } -// -// events -// }; -// -// return (new_position, events, signals); -// } -// } +#[derive(Clone, Debug)] +pub struct TrailingStop { + stop_percentages: HashMap, +} + +impl TrailingStop { + const BREAK_EVEN_PERC: f64 = 0.2; + const MIN_PROFIT_PERC: f64 = TrailingStop::BREAK_EVEN_PERC + 0.3; + const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 2.5; + const MAX_LOSS_PERC: f64 = -1.7; + + const TAKER_FEE: f64 = 0.2; + + pub fn new() -> Self { + TrailingStop { + stop_percentages: HashMap::new(), + } + } + + fn net_pl_percentage(pl: f64, fee: f64) -> f64 { + pl - fee + } +} + +impl PositionStrategy for TrailingStop { + fn on_new_tick( + &self, + position: &Position, + manager: &PositionManager, + ) -> (Position, Vec, Vec) { + let mut signals = vec![]; + let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE); + let events = vec![]; + + let state = { + if pl_perc > TrailingStop::GOOD_PROFIT_PERC { + PositionProfitState::Profit + } else if TrailingStop::MIN_PROFIT_PERC <= pl_perc + && pl_perc < TrailingStop::GOOD_PROFIT_PERC + { + PositionProfitState::MinimumProfit + } else if 0.0 <= pl_perc && pl_perc < TrailingStop::MIN_PROFIT_PERC { + PositionProfitState::BreakEven + } else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 { + PositionProfitState::Loss + } else { + signals.push(SignalKind::ClosePosition { + position_id: position.position_id(), + }); + PositionProfitState::Critical + } + }; + + let opt_pre_pw = manager.position_previous_tick(position.position_id(), None); + let event_metadata = EventMetadata::new(Some(position.position_id()), None); + let new_position = position.clone().with_profit_state(Some(state)); + + match opt_pre_pw { + Some(prev) => { + if prev.profit_state() == Some(state) { + return (new_position, events, signals); + } + } + None => return (new_position, events, signals), + }; + + let events = { + let mut events = vec![]; + + if state == PositionProfitState::Profit { + events.push(Event::new( + EventKind::ReachedGoodProfit, + manager.current_tick(), + Some(event_metadata), + )); + } else if state == PositionProfitState::MinimumProfit { + events.push(Event::new( + EventKind::ReachedMinProfit, + manager.current_tick(), + Some(event_metadata), + )); + } else if state == PositionProfitState::BreakEven { + events.push(Event::new( + EventKind::ReachedBreakEven, + manager.current_tick(), + Some(event_metadata), + )); + } else if state == PositionProfitState::Loss { + events.push(Event::new( + EventKind::ReachedLoss, + manager.current_tick(), + Some(event_metadata), + )); + } else { + events.push(Event::new( + EventKind::ReachedMaxLoss, + manager.current_tick(), + Some(event_metadata), + )); + } + + events + }; + + return (new_position, events, signals); + } +} -- 2.47.2 From f6702f22e6a48e3fdb2e83b57a1589d5d88a693c Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Thu, 14 Jan 2021 19:29:35 +0000 Subject: [PATCH 045/127] pair_statuses -> price_managers --- rustybot/src/bot.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index 3d48ae5..1cd951d 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -35,19 +35,19 @@ impl BfxBot { let mut pos_managers = Vec::new(); let mut order_managers = Vec::new(); - let mut pair_statuses = Vec::new(); + let mut price_managers = Vec::new(); for c in clients { for p in &pairs { pos_managers.push(PositionManager::new(p.clone(), c.clone())); order_managers.push(OrderManager::new(p.clone(), c.clone())); - pair_statuses.push(PriceManager::new(p.clone(), c.clone())); + price_managers.push(PriceManager::new(p.clone(), c.clone())); } } BfxBot { ticker: Ticker::new(tick_duration), - price_managers: pair_statuses, + price_managers, quote, trading_symbols, order_managers, -- 2.47.2 From f707f62ce3802074f862bfe3a72b170ce9f6f8a8 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Fri, 15 Jan 2021 10:40:06 +0000 Subject: [PATCH 046/127] added name into Strategy trait --- rustybot/src/strategy.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index bdf1fa6..d995b87 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -8,6 +8,7 @@ use crate::managers::PositionManager; use crate::models::{Position, PositionProfitState}; pub trait PositionStrategy: DynClone { + fn name(&self) -> String; fn on_new_tick( &self, position: &Position, @@ -17,7 +18,7 @@ pub trait PositionStrategy: DynClone { impl Debug for dyn PositionStrategy { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - write!(f, "PositionStrategy") + write!(f, "{}", self.name()) } } @@ -46,6 +47,10 @@ impl TrailingStop { } impl PositionStrategy for TrailingStop { + fn name(&self) -> String { + "Trailing stop".into() + } + fn on_new_tick( &self, position: &Position, -- 2.47.2 From f541599fed990fc71b2b3b987799885a5b80d599 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Fri, 15 Jan 2021 10:40:36 +0000 Subject: [PATCH 047/127] added name into Connector trait. hardcoded affiliate code --- rustybot/src/connectors.rs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 8f9dd49..c3a675a 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -13,11 +13,7 @@ use crate::BoxError; #[derive(Eq, PartialEq, Hash, Clone, Debug)] pub enum ExchangeKind { - Bitfinex { - api_key: String, - api_secret: String, - affiliate_code: Option, - }, + Bitfinex { api_key: String, api_secret: String }, } /// You do **not** have to wrap the `Client` in an [`Rc`] or [`Arc`] to **reuse** it, @@ -34,9 +30,7 @@ impl Client { ExchangeKind::Bitfinex { api_key, api_secret, - affiliate_code, - } => BitfinexConnector::new(&api_key, &api_secret) - .with_affiliate_code(affiliate_code.clone()), + } => BitfinexConnector::new(&api_key, &api_secret), }; Client { @@ -73,6 +67,7 @@ impl Client { #[async_trait] pub trait Connector: Send + Sync { + fn name(&self) -> String; async fn active_positions(&self, pair: &SymbolPair) -> Result>, BoxError>; async fn current_prices(&self, pair: &SymbolPair) -> Result; async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError>; @@ -87,7 +82,7 @@ pub trait Connector: Send + Sync { impl Debug for dyn Connector { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - write!(f, "Connector") + write!(f, "{}", self.name()) } } @@ -103,18 +98,15 @@ pub struct BitfinexConnector { } impl BitfinexConnector { + const AFFILIATE_CODE: &'static str = "XPebOgHxA"; + pub fn new(api_key: &str, api_secret: &str) -> Self { BitfinexConnector { bfx: Bitfinex::new(Some(api_key.into()), Some(api_secret.into())), - affiliate_code: None, + affiliate_code: Some(BitfinexConnector::AFFILIATE_CODE.into()), } } - pub fn with_affiliate_code(mut self, affiliate_code: Option) -> Self { - self.affiliate_code = affiliate_code; - self - } - fn format_trading_pair(&self, pair: &SymbolPair) -> String { if pair.to_string().to_lowercase().contains("test") { format!("{}:{}", pair.base(), pair.quote()) @@ -126,6 +118,10 @@ impl BitfinexConnector { #[async_trait] impl Connector for BitfinexConnector { + fn name(&self) -> String { + "Bitfinex".into() + } + async fn active_positions(&self, pair: &SymbolPair) -> Result>, BoxError> { let active_positions = self.bfx.positions.active_positions().await?; -- 2.47.2 From befa1d4becbb25302e17a1b5c6eb9c19eb605367 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Fri, 15 Jan 2021 10:40:48 +0000 Subject: [PATCH 048/127] implemented exchange manager --- rustybot/src/bot.rs | 102 +++++++++------------------------------ rustybot/src/main.rs | 4 +- rustybot/src/managers.rs | 94 +++++++++++++++++++++++++++++++++++- 3 files changed, 117 insertions(+), 83 deletions(-) diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index 1cd951d..0a5fc46 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -1,4 +1,5 @@ use core::time::Duration; +use std::collections::HashMap; use log::{debug, error, info}; use tokio::time::delay_for; @@ -6,7 +7,7 @@ use tokio::time::delay_for; use crate::connectors::{Client, ExchangeKind}; use crate::currency::{Symbol, SymbolPair}; use crate::events::Event; -use crate::managers::{OrderManager, PositionManager, PriceManager}; +use crate::managers::{ExchangeManager, OrderManager, PositionManager, PriceManager}; use crate::strategy::PositionStrategy; use crate::ticker::Ticker; use crate::BoxError; @@ -15,9 +16,7 @@ pub struct BfxBot { ticker: Ticker, quote: Symbol, trading_symbols: Vec, - price_managers: Vec, - order_managers: Vec, - pos_managers: Vec, + exchange_managers: Vec, } impl BfxBot { @@ -27,48 +26,36 @@ impl BfxBot { quote: Symbol, tick_duration: Duration, ) -> Self { - let clients: Vec<_> = exchanges.iter().map(|x| Client::new(x)).collect(); let pairs: Vec<_> = trading_symbols .iter() .map(|x| SymbolPair::new(quote.clone(), x.clone())) .collect(); - let mut pos_managers = Vec::new(); - let mut order_managers = Vec::new(); - let mut price_managers = Vec::new(); - - for c in clients { - for p in &pairs { - pos_managers.push(PositionManager::new(p.clone(), c.clone())); - order_managers.push(OrderManager::new(p.clone(), c.clone())); - price_managers.push(PriceManager::new(p.clone(), c.clone())); - } - } + let exchange_managers = exchanges + .iter() + .map(|x| ExchangeManager::new(x, &pairs)) + .collect(); BfxBot { ticker: Ticker::new(tick_duration), - price_managers, quote, trading_symbols, - order_managers, - pos_managers, + exchange_managers, } } pub fn with_position_strategy(mut self, strategy: Box) -> Self { - self.pos_managers = self - .pos_managers + self.exchange_managers = self + .exchange_managers .into_iter() - .map(|x| x.with_strategy(dyn_clone::clone_box(&*strategy))) + .map(|x| x.with_position_strategy(dyn_clone::clone_box(&*strategy))) .collect(); self } pub async fn start_loop(&mut self) -> Result<(), BoxError> { - if let Err(e) = self.update_managers().await { - error!("Error while starting managers: {}", e); - } + self.update_exchanges().await?; loop { info!("Current tick: {}", self.ticker.current_tick()); @@ -79,65 +66,22 @@ impl BfxBot { } } + async fn update_exchanges(&mut self) -> Result<(), BoxError> { + for e in &mut self.exchange_managers { + if let Err(err) = e.update_managers(self.ticker.current_tick()).await { + error!("Error while updating managers: {}", err); + } + } + + Ok(()) + } + async fn update(&mut self) -> Result<(), BoxError> { delay_for(self.ticker.duration()).await; self.ticker.inc(); - if let Err(e) = self.update_managers().await { - error!("Error while updating managers: {}", e); - } + self.update_exchanges().await?; Ok(()) } - - async fn update_managers(&mut self) -> Result<(), BoxError> { - self.update_price_managers().await?; - self.update_position_managers().await?; - - Ok(()) - } - - async fn update_position_managers(&mut self) -> Result>, BoxError> { - for mgr in &mut self.pos_managers { - let tick = self.ticker.current_tick(); - - mgr.update(tick).await?; - } - - Ok(None) - } - - async fn update_price_managers(&mut self) -> Result>, BoxError> { - let futures: Vec<_> = self - .price_managers - .clone() - .into_iter() - // the only reason you need the async block is that the future - // returned by x.update(tick) borrows from x - // so we create a future that first takes ownership of x, then uses it to call x.update - .map(|mut x| { - let tick = self.ticker.current_tick(); - async move { x.update(tick).await } - }) - .map(tokio::spawn) - .collect(); - - let mut price_entries = vec![]; - - for f in futures { - price_entries.push(f.await??); - } - - for manager in &mut self.price_managers { - let prices: Vec<_> = price_entries - .drain_filter(|x| x.pair() == manager.pair()) - .collect(); - - for p in prices { - manager.add_entry(p); - } - } - - Ok(None) - } } diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index a280619..accaf6d 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -26,12 +26,10 @@ async fn main() -> Result<(), BoxError> { let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2"; let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli"; - let affiliate_code = "XPebOgHxA"; let bitfinex = ExchangeKind::Bitfinex { api_key: test_api_key.into(), api_secret: test_api_secret.into(), - affiliate_code: Some(affiliate_code.into()), }; let mut bot = BfxBot::new( @@ -65,7 +63,7 @@ fn setup_logger() -> Result<(), fern::InitError> { .level(Debug) .filter(|metadata| metadata.target().contains("rustybot")) .chain(std::io::stdout()) - .chain(fern::log_file("rustico.log")?) + // .chain(fern::log_file("rustico.log")?) .apply()?; Ok(()) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 4f28192..631137e 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::connectors::Client; +use crate::connectors::{Client, ExchangeKind}; use crate::currency::SymbolPair; use crate::events::{Event, SignalKind}; use crate::models::{Order, Position, PriceTicker}; @@ -245,3 +245,95 @@ impl OrderManager { unimplemented!() } } + +pub struct ExchangeManager { + kind: ExchangeKind, + price_managers: Vec, + order_managers: Vec, + position_managers: Vec, + client: Client, +} + +impl ExchangeManager { + pub fn new(kind: &ExchangeKind, pairs: &Vec) -> Self { + let client = Client::new(kind); + + let mut position_managers = Vec::new(); + let mut order_managers = Vec::new(); + let mut price_managers = Vec::new(); + + for p in pairs { + position_managers.push(PositionManager::new(p.clone(), client.clone())); + order_managers.push(OrderManager::new(p.clone(), client.clone())); + price_managers.push(PriceManager::new(p.clone(), client.clone())); + } + + ExchangeManager { + kind: kind.clone(), + position_managers, + order_managers, + price_managers, + client, + } + } + + pub fn with_position_strategy(mut self, strategy: Box) -> Self { + self.position_managers = self + .position_managers + .into_iter() + .map(|x| x.with_strategy(dyn_clone::clone_box(&*strategy))) + .collect(); + + self + } + + pub async fn update_managers(&mut self, tick: u64) -> Result<(), BoxError> { + self.update_price_managers(tick).await?; + self.update_position_managers(tick).await?; + + Ok(()) + } + + async fn update_position_managers( + &mut self, + tick: u64, + ) -> Result>, BoxError> { + for mgr in &mut self.position_managers { + println!("Manager: {:?}", mgr); + mgr.update(tick).await?; + } + + Ok(None) + } + + async fn update_price_managers(&mut self, tick: u64) -> Result>, BoxError> { + let futures: Vec<_> = self + .price_managers + .clone() + .into_iter() + // the only reason you need the async block is that the future + // returned by x.update(tick) borrows from x + // so we create a future that first takes ownership of x, then uses it to call x.update + .map(|mut x| async move { x.update(tick).await }) + .map(tokio::spawn) + .collect(); + + let mut price_entries = vec![]; + + for f in futures { + price_entries.push(f.await??); + } + + for manager in &mut self.price_managers { + let prices: Vec<_> = price_entries + .drain_filter(|x| x.pair() == manager.pair()) + .collect(); + + for p in prices { + manager.add_entry(p); + } + } + + Ok(None) + } +} -- 2.47.2 From c754708213097a2fd1c31ba7cf601aeb39165a55 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Fri, 15 Jan 2021 10:49:44 +0000 Subject: [PATCH 049/127] added OrderForm struct --- rustybot/src/models.rs | 75 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index e0423ed..c781f45 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -1,4 +1,6 @@ use crate::currency::SymbolPair; +use crate::BoxError; +use std::fmt::Display; /*************** * Prices @@ -62,6 +64,79 @@ pub enum OrderKind { ExchangeIoc, } +#[derive(Serialize, Clone)] +pub struct OrderForm { + /// Order Type: LIMIT, EXCHANGE LIMIT, MARKET, EXCHANGE MARKET, + /// STOP, EXCHANGE STOP, STOP LIMIT, EXCHANGE STOP LIMIT, + /// TRAILING STOP, EXCHANGE TRAILING STOP, FOK, + /// EXCHANGE FOK, IOC, EXCHANGE IOC + kind: OrderKind, + /// Symbol for desired pair + symbol: SymbolPair, + /// Price of order + price: String, + /// Amount of order (positive for buy, negative for sell) + amount: String, + /// Set the leverage for a derivative order, supported by derivative symbol orders only. + /// The value should be between 1 and 100 inclusive. + /// The field is optional, if omitted the default leverage value of 10 will be used. + leverage: Option, + /// The trailing price for a trailing stop order + price_trailing: Option, + /// Auxiliary Limit price (for STOP LIMIT) + price_aux_limit: Option, + /// Time-In-Force: datetime for automatic order cancellation (ie. 2020-01-01 10:45:23) ) + tif: Option, +} + +impl OrderForm { + pub fn new(symbol: &SymbolPair, price: f64, amount: f64, kind: OrderKind) -> Self { + OrderForm { + kind, + symbol: symbol.clone(), + price: price.to_string(), + amount: amount.to_string(), + leverage: None, + price_trailing: None, + price_aux_limit: None, + tif: None, + } + } + + pub fn with_leverage(mut self, leverage: u32) -> Self { + self.leverage = Some(leverage); + self + } + + pub fn with_price_trailing(mut self, trailing: f64) -> Result { + match self.kind { + OrderKind::TrailingStop => { + self.price_trailing = Some(trailing.to_string()); + Ok(self) + } + _ => Err("Invalid order type.".into()), + } + } + + pub fn price_aux_limit(mut self, limit: f64) -> Result { + match self.kind { + OrderKind::StopLimit | OrderKind::ExchangeStopLimit => { + self.price_aux_limit = Some(limit.to_string()); + Ok(self) + } + _ => Err("Invalid order type.".into()), + } + } + + pub fn with_tif(mut self, tif: DateTime) -> Self + where + T::Offset: Display, + { + self.tif = Some(tif.format("%Y-%m-%d %H:%M:%S").to_string()); + self + } +} + /*************** * Positions ***************/ -- 2.47.2 From 50c961ec311f55a5b1e1bb1a77cb05314979f1e9 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Fri, 15 Jan 2021 10:51:02 +0000 Subject: [PATCH 050/127] added chrono --- rustybot/Cargo.lock | 1 + rustybot/Cargo.toml | 3 ++- rustybot/src/models.rs | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/rustybot/Cargo.lock b/rustybot/Cargo.lock index 5025cfb..b3e1cbe 100644 --- a/rustybot/Cargo.lock +++ b/rustybot/Cargo.lock @@ -1128,6 +1128,7 @@ version = "0.1.0" dependencies = [ "async-trait", "bitfinex", + "chrono", "dyn-clone", "fern", "futures-util", diff --git a/rustybot/Cargo.toml b/rustybot/Cargo.toml index e328703..490fa04 100644 --- a/rustybot/Cargo.toml +++ b/rustybot/Cargo.toml @@ -15,4 +15,5 @@ async-trait = "0.1" regex = "1" dyn-clone = "1" log = "0.4" -fern = {version = "0.6", features = ["colored"]} \ No newline at end of file +fern = {version = "0.6", features = ["colored"]} +chrono = "0.4" \ No newline at end of file diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index c781f45..31aa050 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -1,5 +1,6 @@ use crate::currency::SymbolPair; use crate::BoxError; +use chrono::{DateTime, TimeZone}; use std::fmt::Display; /*************** @@ -64,7 +65,7 @@ pub enum OrderKind { ExchangeIoc, } -#[derive(Serialize, Clone)] +#[derive(Clone)] pub struct OrderForm { /// Order Type: LIMIT, EXCHANGE LIMIT, MARKET, EXCHANGE MARKET, /// STOP, EXCHANGE STOP, STOP LIMIT, EXCHANGE STOP LIMIT, -- 2.47.2 From 0d48d3768aa19c6eec43066add085de3a395295c Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Fri, 15 Jan 2021 11:10:00 +0000 Subject: [PATCH 051/127] refactored update for position manager --- rustybot/src/main.rs | 1 + rustybot/src/managers.rs | 76 +++++++++++++++++++++------------------- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index accaf6d..0d42b51 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -1,4 +1,5 @@ #![feature(drain_filter)] +#![feature(bool_to_option)] use fern::colors::{Color, ColoredLevelConfig}; use log::LevelFilter::Debug; diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 631137e..2614211 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use crate::connectors::{Client, ExchangeKind}; use crate::currency::SymbolPair; use crate::events::{Event, SignalKind}; -use crate::models::{Order, Position, PriceTicker}; +use crate::models::{Order, OrderForm, Position, PriceTicker}; use crate::strategy::PositionStrategy; use crate::BoxError; @@ -160,51 +160,55 @@ impl PositionManager { self.current_tick } - pub async fn update(&mut self, tick: u64) -> Result>, BoxError> { + pub async fn update( + &mut self, + tick: u64, + ) -> Result<(Option>, Option), BoxError> { let opt_active_positions = self.client.active_positions(&self.pair).await?; let mut events = vec![]; self.current_tick = tick; - if opt_active_positions.is_none() { - return Ok(None); - } - // we assume there is only ONE active position per pair - match opt_active_positions - .unwrap() - .into_iter() - .filter(|x| x.pair() == &self.pair) - .next() - { - Some(position) => { - // applying strategy to position - let active_position = { - match &self.strategy { - Some(strategy) => { - let (pos, strategy_events, _) = strategy.on_new_tick(&position, &self); + match opt_active_positions { + // no open positions, no events and no order forms returned + None => return Ok((None, None)), - events.extend(strategy_events); - pos - } - None => position, + Some(positions) => { + // checking if there are positions open for our pair + match positions + .into_iter() + .filter(|x| x.pair() == &self.pair) + .next() + { + // no open positions for our pair, setting active position to none + None => self.active_position = None, + + // applying strategy to open position and saving into struct + Some(position) => { + let position_after_strategy = { + match &self.strategy { + Some(strategy) => { + let (pos, strategy_events, _) = + strategy.on_new_tick(&position, &self); + + events.extend(strategy_events); + + pos + } + None => position, + } + }; + + self.positions_history + .insert(self.current_tick(), position_after_strategy.clone()); + self.active_position = Some(position_after_strategy); } - }; - - self.positions_history - .insert(self.current_tick(), active_position.clone()); - self.active_position = Some(active_position); + } } - None => { - self.active_position = None; - } - } + }; - if events.is_empty() { - Ok(None) - } else { - Ok(Some(events)) - } + Ok(((events.is_empty().then_some(events)), None)) } pub fn position_previous_tick(&self, id: u64, tick: Option) -> Option<&Position> { -- 2.47.2 From dfd676612e207b67460e9134da09183454aae6c7 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sat, 16 Jan 2021 11:43:16 +0000 Subject: [PATCH 052/127] stuff... --- rustybot/Cargo.lock | 1 + rustybot/Cargo.toml | 3 +- rustybot/src/connectors.rs | 100 +++++++++++++++++++++++----------- rustybot/src/events.rs | 13 ++--- rustybot/src/managers.rs | 106 ++++++++++++++++++++++++------------- rustybot/src/models.rs | 81 +++++++++++++++++++++++----- rustybot/src/strategy.rs | 69 +++++++++++++++++++----- 7 files changed, 271 insertions(+), 102 deletions(-) diff --git a/rustybot/Cargo.lock b/rustybot/Cargo.lock index b3e1cbe..92cc2d1 100644 --- a/rustybot/Cargo.lock +++ b/rustybot/Cargo.lock @@ -1128,6 +1128,7 @@ version = "0.1.0" dependencies = [ "async-trait", "bitfinex", + "byteorder", "chrono", "dyn-clone", "fern", diff --git a/rustybot/Cargo.toml b/rustybot/Cargo.toml index 490fa04..eecc05d 100644 --- a/rustybot/Cargo.toml +++ b/rustybot/Cargo.toml @@ -16,4 +16,5 @@ regex = "1" dyn-clone = "1" log = "0.4" fern = {version = "0.6", features = ["colored"]} -chrono = "0.4" \ No newline at end of file +chrono = "0.4" +byteorder = "1" \ No newline at end of file diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index c3a675a..c80a17c 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -4,11 +4,11 @@ use std::sync::Arc; use async_trait::async_trait; use bitfinex::api::Bitfinex; -use bitfinex::orders::{OrderForm, OrderMeta}; +use bitfinex::orders::{ActiveOrder, OrderMeta}; use bitfinex::ticker::TradingPairTicker; use crate::currency::SymbolPair; -use crate::models::{Order, OrderKind, Position, PositionState, PriceTicker}; +use crate::models::{ExecutedOrder, OrderForm, OrderKind, Position, PositionState, PriceTicker}; use crate::BoxError; #[derive(Eq, PartialEq, Hash, Clone, Debug)] @@ -50,18 +50,12 @@ impl Client { self.inner.current_prices(pair).await } - pub async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError> { + pub async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError> { self.inner.active_orders(pair).await } - pub async fn submit_order( - &self, - pair: &SymbolPair, - amount: f64, - price: f64, - kind: &OrderKind, - ) -> Result<(), BoxError> { - self.inner.submit_order(pair, amount, price, kind).await + pub async fn submit_order(&self, order: OrderForm) -> Result { + self.inner.submit_order(order).await } } @@ -70,14 +64,8 @@ pub trait Connector: Send + Sync { fn name(&self) -> String; async fn active_positions(&self, pair: &SymbolPair) -> Result>, BoxError>; async fn current_prices(&self, pair: &SymbolPair) -> Result; - async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError>; - async fn submit_order( - &self, - pair: &SymbolPair, - amount: f64, - price: f64, - kind: &OrderKind, - ) -> Result<(), BoxError>; + async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError>; + async fn submit_order(&self, order: OrderForm) -> Result; } impl Debug for dyn Connector { @@ -148,24 +136,28 @@ impl Connector for BitfinexConnector { Ok(ticker) } - async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError> { + async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError> { unimplemented!() } - async fn submit_order( - &self, - pair: &SymbolPair, - amount: f64, - price: f64, - kind: &OrderKind, - ) -> Result<(), BoxError> { + async fn submit_order(&self, order: OrderForm) -> Result { let order_form = match &self.affiliate_code { - Some(affiliate_code) => OrderForm::new(pair.trading_repr(), price, amount, kind.into()) - .with_meta(OrderMeta::new(affiliate_code.clone())), - None => OrderForm::new(pair.trading_repr(), price, amount, kind.into()), + Some(affiliate_code) => bitfinex::orders::OrderForm::new( + order.pair().trading_repr(), + *order.price(), + *order.amount(), + order.kind().into(), + ) + .with_meta(OrderMeta::new(affiliate_code.clone())), + None => bitfinex::orders::OrderForm::new( + order.pair().trading_repr(), + *order.price(), + *order.amount(), + order.kind().into(), + ), }; - Ok(self.bfx.orders.submit_order(&order_form).await?) + Ok(self.bfx.orders.submit_order(&order_form).await?.into()) } } @@ -216,6 +208,26 @@ impl From<&OrderKind> for bitfinex::orders::OrderKind { } } +impl From for bitfinex::orders::OrderKind { + fn from(o: OrderKind) -> Self { + match o { + OrderKind::Limit => Self::Limit, + OrderKind::ExchangeLimit => Self::ExchangeLimit, + OrderKind::Market => Self::Market, + OrderKind::ExchangeMarket => Self::ExchangeMarket, + OrderKind::Stop => Self::Stop, + OrderKind::ExchangeStop => Self::ExchangeStop, + OrderKind::StopLimit => Self::StopLimit, + OrderKind::ExchangeStopLimit => Self::ExchangeStopLimit, + OrderKind::TrailingStop => Self::TrailingStop, + OrderKind::Fok => Self::Fok, + OrderKind::ExchangeFok => Self::ExchangeFok, + OrderKind::Ioc => Self::Ioc, + OrderKind::ExchangeIoc => Self::ExchangeIoc, + } + } +} + impl From for PriceTicker { fn from(t: TradingPairTicker) -> Self { Self { @@ -232,3 +244,29 @@ impl From for PriceTicker { } } } + +impl From for ExecutedOrder { + fn from(o: ActiveOrder) -> Self { + Self { + id: o.id, + group_id: o.group_id, + client_id: o.client_id, + symbol: o.symbol, + creation_timestamp: o.creation_timestamp, + update_timestamp: o.update_timestamp, + amount: o.amount, + amount_original: o.amount_original, + order_type: o.order_type, + previous_order_type: o.previous_order_type, + flags: o.flags, + order_status: o.order_status, + price: o.price, + price_avg: o.price_avg, + price_trailing: o.price_trailing, + price_aux_limit: o.price_aux_limit, + notify: o.notify, + hidden: o.hidden, + placed_id: o.placed_id, + } + } +} diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 6229c3f..4e6868e 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -3,12 +3,12 @@ use std::future::Future; use tokio::task::JoinHandle; -use crate::managers::PriceManager; +use crate::managers::{OrderManager, PositionManager, PriceManager}; use crate::models::{Position, PositionProfitState}; -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[derive(Clone, Debug, Hash, PartialEq, Eq)] pub enum SignalKind { - ClosePosition { position_id: u64 }, + ClosePosition(Position), OpenPosition, } @@ -74,18 +74,19 @@ impl Event { } } -pub struct EventDispatcher { +pub struct Dispatcher { event_handlers: HashMap JoinHandle<()>>>>, profit_state_handlers: HashMap JoinHandle<()>>>>, signal_handlers: HashMap JoinHandle<()>>>>, + on_any_event_handlers: Vec JoinHandle<()>>>, on_any_profit_state_handlers: Vec JoinHandle<()>>>, } -impl EventDispatcher { +impl Dispatcher { pub fn new() -> Self { - EventDispatcher { + Dispatcher { event_handlers: HashMap::new(), profit_state_handlers: HashMap::new(), signal_handlers: HashMap::new(), diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 2614211..5c636dd 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -1,10 +1,14 @@ use std::collections::HashMap; +use std::ops::Neg; + +use bitfinex::ticker::TradingPairTicker; +use log::error; use crate::connectors::{Client, ExchangeKind}; use crate::currency::SymbolPair; -use crate::events::{Event, SignalKind}; -use crate::models::{Order, OrderForm, Position, PriceTicker}; -use crate::strategy::PositionStrategy; +use crate::events::{Dispatcher, Event, SignalKind}; +use crate::models::{ExecutedOrder, OrderForm, OrderKind, Position, PriceTicker}; +use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy}; use crate::BoxError; pub struct EventManager { @@ -43,34 +47,6 @@ impl PriceManager { )) } - // pub fn add_position(&mut self, position: Position) { - // let (new_position, events, signals) = { - // match &self.strategy { - // Some(strategy) => strategy.on_new_tick(&position, &self), - // None => (position, vec![], vec![]), - // } - // }; - // - // self.positions - // .entry(self.current_tick) - // .or_default() - // .push(new_position.clone()); - // - // // calling position state callbacks - // self.dispatcher - // .call_position_state_handlers(&new_position, &self); - // - // // adding events and calling callbacks - // for e in events { - // self.add_event(e); - // } - // - // // adding signals to current tick vector - // for s in signals { - // self.add_signal(s); - // } - // } - // fn add_event(&mut self, event: Event) { // self.events.push(event); // @@ -190,7 +166,7 @@ impl PositionManager { match &self.strategy { Some(strategy) => { let (pos, strategy_events, _) = - strategy.on_new_tick(&position, &self); + strategy.on_new_tick(position, &self); events.extend(strategy_events); @@ -225,29 +201,81 @@ impl PositionManager { self.positions_history .get(&tick) - .filter(|x| x.position_id() == id) + .filter(|x| x.id() == id) .and_then(|x| Some(x)) } } pub struct OrderManager { + tracked_positions: HashMap, pair: SymbolPair, - open_orders: Vec, + open_orders: Vec, client: Client, + strategy: Box, } impl OrderManager { - pub fn new(pair: SymbolPair, client: Client) -> Self { + const UNDERCUT_PERC: f64 = 0.005; + + pub fn new(pair: SymbolPair, client: Client, strategy: Box) -> Self { OrderManager { pair, open_orders: Vec::new(), client, + strategy, + tracked_positions: HashMap::new(), } } + pub async fn close_position(&mut self, position: &Position) -> Result<(), BoxError> { + // checking if the position has an open order. + // If so, the strategy method is called, otherwise we open + // an undercut limit order at the best current price. + match self.tracked_positions.get(&position.id()) { + Some(open_order) => self.strategy.on_position_close(open_order, &self), + None => { + let current_prices = self.client.current_prices(&self.pair).await?; + let closing_price = self.best_closing_price(&position, ¤t_prices)?; + + // submitting order + let order_form = OrderForm::new( + &self.pair, + closing_price, + position.amount().neg(), + OrderKind::Limit, + ); + + match self.client.submit_order(order_form).await { + Err(e) => error!("Could not submit order: {}", e), + Ok(o) => { + self.tracked_positions.insert(position.id(), o); + } + }; + } + } + + Ok(()) + } + pub fn update(&self) -> Option> { unimplemented!() } + + pub fn best_closing_price( + &self, + position: &Position, + price_ticker: &TradingPairTicker, + ) -> Result { + let price = { + if position.is_short() { + price_ticker.ask + } else { + price_ticker.bid + } + }; + + Ok(price * (1.0 - OrderManager::UNDERCUT_PERC)) + } } pub struct ExchangeManager { @@ -255,6 +283,7 @@ pub struct ExchangeManager { price_managers: Vec, order_managers: Vec, position_managers: Vec, + dispatcher: Dispatcher, client: Client, } @@ -268,7 +297,11 @@ impl ExchangeManager { for p in pairs { position_managers.push(PositionManager::new(p.clone(), client.clone())); - order_managers.push(OrderManager::new(p.clone(), client.clone())); + order_managers.push(OrderManager::new( + p.clone(), + client.clone(), + Box::new(FastOrderStrategy {}), + )); price_managers.push(PriceManager::new(p.clone(), client.clone())); } @@ -278,6 +311,7 @@ impl ExchangeManager { order_managers, price_managers, client, + dispatcher: Dispatcher::new(), } } diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index 31aa050..e2b53cb 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -1,7 +1,10 @@ +use std::fmt::Display; + +use chrono::{DateTime, TimeZone}; + use crate::currency::SymbolPair; use crate::BoxError; -use chrono::{DateTime, TimeZone}; -use std::fmt::Display; +use std::hash::{Hash, Hasher}; /*************** * Prices @@ -26,7 +29,7 @@ pub struct PriceTicker { ***************/ #[derive(Clone, Debug)] -pub struct Order { +pub struct ExecutedOrder { pub id: i64, pub group_id: Option, pub client_id: i64, @@ -48,7 +51,13 @@ pub struct Order { pub placed_id: Option, } -#[derive(Copy, Clone, Debug)] +impl Hash for ExecutedOrder { + fn hash(&self, state: &mut H) { + state.write(&self.id.to_le_bytes()) + } +} + +#[derive(Copy, Clone, Debug, Hash)] pub enum OrderKind { Limit, ExchangeLimit, @@ -73,11 +82,11 @@ pub struct OrderForm { /// EXCHANGE FOK, IOC, EXCHANGE IOC kind: OrderKind, /// Symbol for desired pair - symbol: SymbolPair, + pair: SymbolPair, /// Price of order - price: String, + price: f64, /// Amount of order (positive for buy, negative for sell) - amount: String, + amount: f64, /// Set the leverage for a derivative order, supported by derivative symbol orders only. /// The value should be between 1 and 100 inclusive. /// The field is optional, if omitted the default leverage value of 10 will be used. @@ -91,12 +100,12 @@ pub struct OrderForm { } impl OrderForm { - pub fn new(symbol: &SymbolPair, price: f64, amount: f64, kind: OrderKind) -> Self { + pub fn new(pair: &SymbolPair, price: f64, amount: f64, kind: OrderKind) -> Self { OrderForm { kind, - symbol: symbol.clone(), - price: price.to_string(), - amount: amount.to_string(), + pair: pair.clone(), + price, + amount, leverage: None, price_trailing: None, price_aux_limit: None, @@ -119,7 +128,7 @@ impl OrderForm { } } - pub fn price_aux_limit(mut self, limit: f64) -> Result { + pub fn with_price_aux_limit(mut self, limit: f64) -> Result { match self.kind { OrderKind::StopLimit | OrderKind::ExchangeStopLimit => { self.price_aux_limit = Some(limit.to_string()); @@ -136,13 +145,38 @@ impl OrderForm { self.tif = Some(tif.format("%Y-%m-%d %H:%M:%S").to_string()); self } + + pub fn kind(&self) -> OrderKind { + self.kind + } + pub fn pair(&self) -> &SymbolPair { + &self.pair + } + pub fn price(&self) -> &f64 { + &self.price + } + pub fn amount(&self) -> &f64 { + &self.amount + } + pub fn leverage(&self) -> Option { + self.leverage + } + pub fn price_trailing(&self) -> &Option { + &self.price_trailing + } + pub fn price_aux_limit(&self) -> &Option { + &self.price_aux_limit + } + pub fn tif(&self) -> &Option { + &self.tif + } } /*************** * Positions ***************/ -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub struct Position { pair: SymbolPair, state: PositionState, @@ -219,7 +253,7 @@ impl Position { pub fn price_liq(&self) -> f64 { self.price_liq } - pub fn position_id(&self) -> u64 { + pub fn id(&self) -> u64 { self.position_id } pub fn profit_state(&self) -> Option { @@ -231,8 +265,27 @@ impl Position { pub fn creation_update(&self) -> Option { self.creation_update } + pub fn is_short(&self) -> bool { + self.amount.is_sign_negative() + } + pub fn is_long(&self) -> bool { + self.amount.is_sign_positive() + } } +impl Hash for Position { + fn hash(&self, state: &mut H) { + state.write(&self.id().to_le_bytes()) + } +} + +impl PartialEq for Position { + fn eq(&self, other: &Self) -> bool { + self.id() == other.id() + } +} +impl Eq for Position {} + #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub enum PositionProfitState { Critical, diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index d995b87..8a58c02 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -4,16 +4,20 @@ use std::fmt::{Debug, Formatter}; use dyn_clone::DynClone; use crate::events::{Event, EventKind, EventMetadata, SignalKind}; -use crate::managers::PositionManager; -use crate::models::{Position, PositionProfitState}; +use crate::managers::{OrderManager, PositionManager}; +use crate::models::{ExecutedOrder, OrderForm, Position, PositionProfitState}; + +/*************** +* DEFINITIONS +***************/ pub trait PositionStrategy: DynClone { fn name(&self) -> String; fn on_new_tick( &self, - position: &Position, + position: Position, manager: &PositionManager, - ) -> (Position, Vec, Vec); + ) -> (Position, Vec, Option); } impl Debug for dyn PositionStrategy { @@ -22,6 +26,27 @@ impl Debug for dyn PositionStrategy { } } +pub trait OrderStrategy: DynClone { + /// The name of the strategy, used for debugging purposes + fn name(&self) -> String; + /// This method is called when the OrderManager checks the open orders on a new tick. + /// It should manage if some orders have to be closed or keep open. + fn on_update(&self); + /// This method is called when the OrderManager is requested to close + /// a position that has an open order associated to it. + fn on_position_close(&self, order: &ExecutedOrder, manager: &mut OrderManager); +} + +impl Debug for dyn OrderStrategy { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.name()) + } +} + +/*************** +* IMPLEMENTATIONS +***************/ + #[derive(Clone, Debug)] pub struct TrailingStop { stop_percentages: HashMap, @@ -53,12 +78,13 @@ impl PositionStrategy for TrailingStop { fn on_new_tick( &self, - position: &Position, + position: Position, manager: &PositionManager, - ) -> (Position, Vec, Vec) { + ) -> (Position, Vec, Option) { let mut signals = vec![]; let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE); let events = vec![]; + let mut order = None; let state = { if pl_perc > TrailingStop::GOOD_PROFIT_PERC { @@ -72,24 +98,22 @@ impl PositionStrategy for TrailingStop { } else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 { PositionProfitState::Loss } else { - signals.push(SignalKind::ClosePosition { - position_id: position.position_id(), - }); + signals.push(SignalKind::ClosePosition(position.clone())); PositionProfitState::Critical } }; - let opt_pre_pw = manager.position_previous_tick(position.position_id(), None); - let event_metadata = EventMetadata::new(Some(position.position_id()), None); + let opt_pre_pw = manager.position_previous_tick(position.id(), None); + let event_metadata = EventMetadata::new(Some(position.id()), None); let new_position = position.clone().with_profit_state(Some(state)); match opt_pre_pw { Some(prev) => { if prev.profit_state() == Some(state) { - return (new_position, events, signals); + return (new_position, events, order); } } - None => return (new_position, events, signals), + None => return (new_position, events, order), }; let events = { @@ -130,6 +154,23 @@ impl PositionStrategy for TrailingStop { events }; - return (new_position, events, signals); + return (new_position, events, order); + } +} + +#[derive(Clone, Debug)] +pub struct FastOrderStrategy {} + +impl OrderStrategy for FastOrderStrategy { + fn name(&self) -> String { + "Fast order strategy".into() + } + + fn on_update(&self) { + unimplemented!() + } + + fn on_position_close(&self, order: &ExecutedOrder, manager: &mut OrderManager) { + unimplemented!() } } -- 2.47.2 From 268000b2180fdc8cbbc71053a47ff896ba971243 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sat, 16 Jan 2021 19:51:13 +0000 Subject: [PATCH 053/127] ordermanager stub --- rustybot/src/managers.rs | 25 ++++++++++++++++++++----- rustybot/src/strategy.rs | 14 +++++++++++--- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 5c636dd..8977d43 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -10,6 +10,7 @@ use crate::events::{Dispatcher, Event, SignalKind}; use crate::models::{ExecutedOrder, OrderForm, OrderKind, Position, PriceTicker}; use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy}; use crate::BoxError; +use tokio::sync::mpsc::Receiver; pub struct EventManager { events: Vec, @@ -206,8 +207,11 @@ impl PositionManager { } } +pub type TrackedPositionsMap = HashMap; + pub struct OrderManager { - tracked_positions: HashMap, + // receiver: Receiver, + tracked_positions: TrackedPositionsMap, pair: SymbolPair, open_orders: Vec, client: Client, @@ -217,8 +221,14 @@ pub struct OrderManager { impl OrderManager { const UNDERCUT_PERC: f64 = 0.005; - pub fn new(pair: SymbolPair, client: Client, strategy: Box) -> Self { + pub fn new( + // receiver: Receiver, + pair: SymbolPair, + client: Client, + strategy: Box, + ) -> Self { OrderManager { + // receiver, pair, open_orders: Vec::new(), client, @@ -228,11 +238,17 @@ impl OrderManager { } pub async fn close_position(&mut self, position: &Position) -> Result<(), BoxError> { + let open_order = self.tracked_positions.get(&position.id()); + // checking if the position has an open order. // If so, the strategy method is called, otherwise we open // an undercut limit order at the best current price. - match self.tracked_positions.get(&position.id()) { - Some(open_order) => self.strategy.on_position_close(open_order, &self), + match open_order { + Some(open_order) => { + self.tracked_positions = self + .strategy + .on_position_close(open_order, &self.tracked_positions); + } None => { let current_prices = self.client.current_prices(&self.pair).await?; let closing_price = self.best_closing_price(&position, ¤t_prices)?; @@ -337,7 +353,6 @@ impl ExchangeManager { tick: u64, ) -> Result>, BoxError> { for mgr in &mut self.position_managers { - println!("Manager: {:?}", mgr); mgr.update(tick).await?; } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 8a58c02..fb1dce1 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -4,7 +4,7 @@ use std::fmt::{Debug, Formatter}; use dyn_clone::DynClone; use crate::events::{Event, EventKind, EventMetadata, SignalKind}; -use crate::managers::{OrderManager, PositionManager}; +use crate::managers::{OrderManager, PositionManager, TrackedPositionsMap}; use crate::models::{ExecutedOrder, OrderForm, Position, PositionProfitState}; /*************** @@ -34,7 +34,11 @@ pub trait OrderStrategy: DynClone { fn on_update(&self); /// This method is called when the OrderManager is requested to close /// a position that has an open order associated to it. - fn on_position_close(&self, order: &ExecutedOrder, manager: &mut OrderManager); + fn on_position_close( + &self, + order: &ExecutedOrder, + tracked_positions: &HashMap, + ) -> TrackedPositionsMap; } impl Debug for dyn OrderStrategy { @@ -170,7 +174,11 @@ impl OrderStrategy for FastOrderStrategy { unimplemented!() } - fn on_position_close(&self, order: &ExecutedOrder, manager: &mut OrderManager) { + fn on_position_close( + &self, + order: &ExecutedOrder, + tracked_positions: &HashMap, + ) -> TrackedPositionsMap { unimplemented!() } } -- 2.47.2 From 03e9c94b3b730fa73a8f4481be0f8fa78a8c8e99 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sat, 16 Jan 2021 21:38:00 +0000 Subject: [PATCH 054/127] actor model stub + futures unordered --- rustybot/src/events.rs | 1 + rustybot/src/managers.rs | 91 ++++++++++++++++++++++++++-------------- 2 files changed, 60 insertions(+), 32 deletions(-) diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 4e6868e..e2064bb 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -8,6 +8,7 @@ use crate::models::{Position, PositionProfitState}; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub enum SignalKind { + Update(u64), ClosePosition(Position), OpenPosition, } diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 8977d43..a89c14a 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -2,7 +2,9 @@ use std::collections::HashMap; use std::ops::Neg; use bitfinex::ticker::TradingPairTicker; -use log::error; +use log::{debug, error}; +use tokio::sync::mpsc::channel; +use tokio::sync::mpsc::{Receiver, Sender}; use crate::connectors::{Client, ExchangeKind}; use crate::currency::SymbolPair; @@ -10,28 +12,68 @@ use crate::events::{Dispatcher, Event, SignalKind}; use crate::models::{ExecutedOrder, OrderForm, OrderKind, Position, PriceTicker}; use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy}; use crate::BoxError; -use tokio::sync::mpsc::Receiver; +use futures_util::stream::FuturesUnordered; +use futures_util::StreamExt; pub struct EventManager { events: Vec, } -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct PriceManager { + receiver: Receiver, pair: SymbolPair, prices: Vec, client: Client, } -impl PriceManager { +async fn run_price_manager(mut manager: PriceManager) { + while let Some(msg) = manager.receiver.recv().await { + manager.handle_message(msg).await.unwrap(); + } +} + +pub struct PriceManagerHandle { + sender: Sender, +} + +impl PriceManagerHandle { pub fn new(pair: SymbolPair, client: Client) -> Self { + let (sender, receiver) = channel(8); + + let price_manager = PriceManager::new(receiver, pair, client); + tokio::spawn(run_price_manager(price_manager)); + + Self { sender } + } + + pub async fn update(&mut self, tick: u64) { + self.sender.send(SignalKind::Update(tick)).await.unwrap(); + } +} + +impl PriceManager { + pub fn new(receiver: Receiver, pair: SymbolPair, client: Client) -> Self { PriceManager { + receiver, pair, prices: Vec::new(), client, } } + pub async fn handle_message(&mut self, message: SignalKind) -> Result<(), BoxError> { + match message { + SignalKind::Update(tick) => { + let a = self.update(tick).await?; + self.add_entry(a); + } + _ => {} + } + + Ok(()) + } + pub fn add_entry(&mut self, entry: PriceEntry) { self.prices.push(entry); } @@ -296,7 +338,7 @@ impl OrderManager { pub struct ExchangeManager { kind: ExchangeKind, - price_managers: Vec, + price_managers: Vec, order_managers: Vec, position_managers: Vec, dispatcher: Dispatcher, @@ -318,7 +360,7 @@ impl ExchangeManager { client.clone(), Box::new(FastOrderStrategy {}), )); - price_managers.push(PriceManager::new(p.clone(), client.clone())); + price_managers.push(PriceManagerHandle::new(p.clone(), client.clone())); } ExchangeManager { @@ -352,40 +394,25 @@ impl ExchangeManager { &mut self, tick: u64, ) -> Result>, BoxError> { - for mgr in &mut self.position_managers { - mgr.update(tick).await?; - } + let mut futures: FuturesUnordered<_> = self + .position_managers + .iter_mut() + .map(|x| x.update(tick)) + .collect(); + + while let Some(x) = futures.next().await {} Ok(None) } async fn update_price_managers(&mut self, tick: u64) -> Result>, BoxError> { - let futures: Vec<_> = self + let mut futures: FuturesUnordered<_> = self .price_managers - .clone() - .into_iter() - // the only reason you need the async block is that the future - // returned by x.update(tick) borrows from x - // so we create a future that first takes ownership of x, then uses it to call x.update - .map(|mut x| async move { x.update(tick).await }) - .map(tokio::spawn) + .iter_mut() + .map(|x| x.update(tick)) .collect(); - let mut price_entries = vec![]; - - for f in futures { - price_entries.push(f.await??); - } - - for manager in &mut self.price_managers { - let prices: Vec<_> = price_entries - .drain_filter(|x| x.pair() == manager.pair()) - .collect(); - - for p in prices { - manager.add_entry(p); - } - } + while let Some(x) = futures.next().await {} Ok(None) } -- 2.47.2 From 503c542a5f520afd51e3b31b2a79533e04d011fd Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sun, 17 Jan 2021 18:18:16 +0000 Subject: [PATCH 055/127] positionmanager is now an actor as well --- rustybot/src/bot.rs | 10 ---- rustybot/src/main.rs | 3 +- rustybot/src/managers.rs | 120 +++++++++++++++++++++++++-------------- rustybot/src/strategy.rs | 4 +- 4 files changed, 81 insertions(+), 56 deletions(-) diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index 0a5fc46..c8f84dd 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -44,16 +44,6 @@ impl BfxBot { } } - pub fn with_position_strategy(mut self, strategy: Box) -> Self { - self.exchange_managers = self - .exchange_managers - .into_iter() - .map(|x| x.with_position_strategy(dyn_clone::clone_box(&*strategy))) - .collect(); - - self - } - pub async fn start_loop(&mut self) -> Result<(), BoxError> { self.update_exchanges().await?; diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index 0d42b51..1dde9fc 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -38,8 +38,7 @@ async fn main() -> Result<(), BoxError> { vec![Symbol::TESTBTC], Symbol::TESTUSD, Duration::new(1, 0), - ) - .with_position_strategy(Box::new(TrailingStop::new())); + ); Ok(bot.start_loop().await?) } diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index a89c14a..e8d01a2 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -2,7 +2,10 @@ use std::collections::HashMap; use std::ops::Neg; use bitfinex::ticker::TradingPairTicker; +use futures_util::stream::FuturesUnordered; +use futures_util::StreamExt; use log::{debug, error}; +use tokio::signal::unix::Signal; use tokio::sync::mpsc::channel; use tokio::sync::mpsc::{Receiver, Sender}; @@ -10,15 +13,17 @@ use crate::connectors::{Client, ExchangeKind}; use crate::currency::SymbolPair; use crate::events::{Dispatcher, Event, SignalKind}; use crate::models::{ExecutedOrder, OrderForm, OrderKind, Position, PriceTicker}; -use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy}; +use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop}; use crate::BoxError; -use futures_util::stream::FuturesUnordered; -use futures_util::StreamExt; pub struct EventManager { events: Vec, } +/****************** +* PRICES +******************/ + #[derive(Debug)] pub struct PriceManager { receiver: Receiver, @@ -27,22 +32,22 @@ pub struct PriceManager { client: Client, } -async fn run_price_manager(mut manager: PriceManager) { - while let Some(msg) = manager.receiver.recv().await { - manager.handle_message(msg).await.unwrap(); - } -} - pub struct PriceManagerHandle { sender: Sender, } impl PriceManagerHandle { + async fn run_price_manager(mut manager: PriceManager) { + while let Some(msg) = manager.receiver.recv().await { + manager.handle_message(msg).await.unwrap(); + } + } + pub fn new(pair: SymbolPair, client: Client) -> Self { let (sender, receiver) = channel(8); let price_manager = PriceManager::new(receiver, pair, client); - tokio::spawn(run_price_manager(price_manager)); + tokio::spawn(PriceManagerHandle::run_price_manager(price_manager)); Self { sender } } @@ -148,37 +153,79 @@ impl PriceEntry { } } +/****************** +* POSITIONS +******************/ + +pub struct PositionManagerHandle { + sender: Sender, +} + +impl PositionManagerHandle { + async fn run_position_manager(mut manager: PositionManager) { + while let Some(msg) = manager.receiver.recv().await { + manager.handle_message(msg).await.unwrap(); + } + } + + pub fn new(pair: SymbolPair, client: Client, strategy: Box) -> Self { + let (sender, receiver) = channel(8); + + let manager = PositionManager::new(receiver, pair, client, strategy); + + tokio::spawn(PositionManagerHandle::run_position_manager(manager)); + + Self { sender } + } + + pub async fn update(&mut self, tick: u64) { + self.sender.send(SignalKind::Update(tick)).await.unwrap(); + } +} + #[derive(Debug)] pub struct PositionManager { + receiver: Receiver, current_tick: u64, pair: SymbolPair, positions_history: HashMap, active_position: Option, client: Client, - strategy: Option>, + strategy: Box, } impl PositionManager { - pub fn new(pair: SymbolPair, client: Client) -> Self { + pub fn new( + receiver: Receiver, + pair: SymbolPair, + client: Client, + strategy: Box, + ) -> Self { PositionManager { + receiver, current_tick: 0, pair, positions_history: HashMap::new(), active_position: None, client, - strategy: None, + strategy, } } - pub fn with_strategy(mut self, strategy: Box) -> Self { - self.strategy = Some(strategy); - self - } - pub fn current_tick(&self) -> u64 { self.current_tick } + pub async fn handle_message(&mut self, msg: SignalKind) -> Result<(), BoxError> { + match msg { + SignalKind::Update(tick) => { + self.update(tick).await?; + } + _ => {} + }; + + Ok(()) + } pub async fn update( &mut self, tick: u64, @@ -205,19 +252,10 @@ impl PositionManager { // applying strategy to open position and saving into struct Some(position) => { - let position_after_strategy = { - match &self.strategy { - Some(strategy) => { - let (pos, strategy_events, _) = - strategy.on_new_tick(position, &self); + let (position_after_strategy, strategy_events, _) = + self.strategy.on_new_tick(position, &self); - events.extend(strategy_events); - - pos - } - None => position, - } - }; + events.extend(strategy_events); self.positions_history .insert(self.current_tick(), position_after_strategy.clone()); @@ -249,6 +287,10 @@ impl PositionManager { } } +/****************** +* ORDERS +******************/ + pub type TrackedPositionsMap = HashMap; pub struct OrderManager { @@ -340,7 +382,7 @@ pub struct ExchangeManager { kind: ExchangeKind, price_managers: Vec, order_managers: Vec, - position_managers: Vec, + position_managers: Vec, dispatcher: Dispatcher, client: Client, } @@ -354,7 +396,11 @@ impl ExchangeManager { let mut price_managers = Vec::new(); for p in pairs { - position_managers.push(PositionManager::new(p.clone(), client.clone())); + position_managers.push(PositionManagerHandle::new( + p.clone(), + client.clone(), + Box::new(TrailingStop::new()), + )); order_managers.push(OrderManager::new( p.clone(), client.clone(), @@ -373,16 +419,6 @@ impl ExchangeManager { } } - pub fn with_position_strategy(mut self, strategy: Box) -> Self { - self.position_managers = self - .position_managers - .into_iter() - .map(|x| x.with_strategy(dyn_clone::clone_box(&*strategy))) - .collect(); - - self - } - pub async fn update_managers(&mut self, tick: u64) -> Result<(), BoxError> { self.update_price_managers(tick).await?; self.update_position_managers(tick).await?; diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index fb1dce1..e238a8a 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -11,7 +11,7 @@ use crate::models::{ExecutedOrder, OrderForm, Position, PositionProfitState}; * DEFINITIONS ***************/ -pub trait PositionStrategy: DynClone { +pub trait PositionStrategy: DynClone + Send { fn name(&self) -> String; fn on_new_tick( &self, @@ -26,7 +26,7 @@ impl Debug for dyn PositionStrategy { } } -pub trait OrderStrategy: DynClone { +pub trait OrderStrategy: DynClone + Send { /// The name of the strategy, used for debugging purposes fn name(&self) -> String; /// This method is called when the OrderManager checks the open orders on a new tick. -- 2.47.2 From 71273ccc7805bc58bd6e0cd5c188d21cb84ab30b Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sun, 17 Jan 2021 18:25:16 +0000 Subject: [PATCH 056/127] order manager is an actor --- rustybot/src/managers.rs | 55 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index e8d01a2..a848599 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -293,8 +293,41 @@ impl PositionManager { pub type TrackedPositionsMap = HashMap; +pub struct OrderManagerHandle { + sender: Sender, +} + +impl OrderManagerHandle { + async fn run_order_manager(mut manager: OrderManager) { + while let Some(msg) = manager.receiver.recv().await { + manager.handle_message(msg).await.unwrap(); + } + } + + pub fn new(pair: SymbolPair, client: Client, strategy: Box) -> Self { + let (sender, receiver) = channel(8); + + let manager = OrderManager::new(receiver, pair, client, strategy); + + tokio::spawn(OrderManagerHandle::run_order_manager(manager)); + + Self { sender } + } + + pub async fn update(&mut self, tick: u64) { + self.sender.send(SignalKind::Update(tick)).await.unwrap(); + } + + pub async fn close_position(&mut self, position: Position) { + self.sender + .send(SignalKind::ClosePosition(position)) + .await + .unwrap(); + } +} + pub struct OrderManager { - // receiver: Receiver, + receiver: Receiver, tracked_positions: TrackedPositionsMap, pair: SymbolPair, open_orders: Vec, @@ -306,13 +339,13 @@ impl OrderManager { const UNDERCUT_PERC: f64 = 0.005; pub fn new( - // receiver: Receiver, + receiver: Receiver, pair: SymbolPair, client: Client, strategy: Box, ) -> Self { OrderManager { - // receiver, + receiver, pair, open_orders: Vec::new(), client, @@ -321,6 +354,18 @@ impl OrderManager { } } + pub async fn handle_message(&mut self, msg: SignalKind) -> Result<(), BoxError> { + match msg { + SignalKind::Update(_) => { + self.update(); + } + SignalKind::ClosePosition(position) => self.close_position(&position).await?, + _ => {} + }; + + Ok(()) + } + pub async fn close_position(&mut self, position: &Position) -> Result<(), BoxError> { let open_order = self.tracked_positions.get(&position.id()); @@ -381,7 +426,7 @@ impl OrderManager { pub struct ExchangeManager { kind: ExchangeKind, price_managers: Vec, - order_managers: Vec, + order_managers: Vec, position_managers: Vec, dispatcher: Dispatcher, client: Client, @@ -401,7 +446,7 @@ impl ExchangeManager { client.clone(), Box::new(TrailingStop::new()), )); - order_managers.push(OrderManager::new( + order_managers.push(OrderManagerHandle::new( p.clone(), client.clone(), Box::new(FastOrderStrategy {}), -- 2.47.2 From f3cb051535d5561e143a163b5ee73b8414945cac Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sun, 17 Jan 2021 21:06:18 +0000 Subject: [PATCH 057/127] pair manager to be constructed. update function result signatures updated THEY HAVE TO BE CONNECTED --- rustybot/src/managers.rs | 43 +++++++++++++++++++++++----------------- rustybot/src/strategy.rs | 30 ++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 26 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index a848599..014a2a8 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -4,7 +4,7 @@ use std::ops::Neg; use bitfinex::ticker::TradingPairTicker; use futures_util::stream::FuturesUnordered; use futures_util::StreamExt; -use log::{debug, error}; +use log::{debug, error, info}; use tokio::signal::unix::Signal; use tokio::sync::mpsc::channel; use tokio::sync::mpsc::{Receiver, Sender}; @@ -16,6 +16,8 @@ use crate::models::{ExecutedOrder, OrderForm, OrderKind, Position, PriceTicker}; use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop}; use crate::BoxError; +type OptionUpdate = (Option>, Option>); + pub struct EventManager { events: Vec, } @@ -226,10 +228,8 @@ impl PositionManager { Ok(()) } - pub async fn update( - &mut self, - tick: u64, - ) -> Result<(Option>, Option), BoxError> { + + pub async fn update(&mut self, tick: u64) -> Result { let opt_active_positions = self.client.active_positions(&self.pair).await?; let mut events = vec![]; @@ -265,7 +265,7 @@ impl PositionManager { } }; - Ok(((events.is_empty().then_some(events)), None)) + Ok((None, None)) } pub fn position_previous_tick(&self, id: u64, tick: Option) -> Option<&Position> { @@ -369,19 +369,26 @@ impl OrderManager { pub async fn close_position(&mut self, position: &Position) -> Result<(), BoxError> { let open_order = self.tracked_positions.get(&position.id()); + info!("Closing position {}", position.id()); + // checking if the position has an open order. // If so, the strategy method is called, otherwise we open // an undercut limit order at the best current price. match open_order { Some(open_order) => { + info!("There is an open order. Calling strategy."); + self.tracked_positions = self .strategy .on_position_close(open_order, &self.tracked_positions); } None => { + info!("Getting current prices..."); let current_prices = self.client.current_prices(&self.pair).await?; + info!("Calculating best closing price..."); let closing_price = self.best_closing_price(&position, ¤t_prices)?; + info!("Submitting order..."); // submitting order let order_form = OrderForm::new( &self.pair, @@ -394,6 +401,7 @@ impl OrderManager { Err(e) => error!("Could not submit order: {}", e), Ok(o) => { self.tracked_positions.insert(position.id(), o); + info!("Done!"); } }; } @@ -402,7 +410,7 @@ impl OrderManager { Ok(()) } - pub fn update(&self) -> Option> { + pub fn update(&self) -> Result { unimplemented!() } @@ -464,17 +472,16 @@ impl ExchangeManager { } } - pub async fn update_managers(&mut self, tick: u64) -> Result<(), BoxError> { - self.update_price_managers(tick).await?; - self.update_position_managers(tick).await?; + pub async fn update_managers(&mut self, tick: u64) -> Result { + let (price_opt_events, price_opt_signals) = self.update_price_managers(tick).await?; + let (pos_opt_events, pos_opt_signals) = self.update_position_managers(tick).await?; - Ok(()) + debug!("{:?}", pos_opt_signals); + + Ok((pos_opt_events, price_opt_signals)) } - async fn update_position_managers( - &mut self, - tick: u64, - ) -> Result>, BoxError> { + async fn update_position_managers(&mut self, tick: u64) -> Result { let mut futures: FuturesUnordered<_> = self .position_managers .iter_mut() @@ -483,10 +490,10 @@ impl ExchangeManager { while let Some(x) = futures.next().await {} - Ok(None) + Ok((None, None)) } - async fn update_price_managers(&mut self, tick: u64) -> Result>, BoxError> { + async fn update_price_managers(&mut self, tick: u64) -> Result { let mut futures: FuturesUnordered<_> = self .price_managers .iter_mut() @@ -495,6 +502,6 @@ impl ExchangeManager { while let Some(x) = futures.next().await {} - Ok(None) + Ok((None, None)) } } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index e238a8a..c1b27ff 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -17,7 +17,7 @@ pub trait PositionStrategy: DynClone + Send { &self, position: Position, manager: &PositionManager, - ) -> (Position, Vec, Option); + ) -> (Position, Option>, Option>); } impl Debug for dyn PositionStrategy { @@ -60,7 +60,7 @@ impl TrailingStop { const BREAK_EVEN_PERC: f64 = 0.2; const MIN_PROFIT_PERC: f64 = TrailingStop::BREAK_EVEN_PERC + 0.3; const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 2.5; - const MAX_LOSS_PERC: f64 = -1.7; + const MAX_LOSS_PERC: f64 = -0.01; const TAKER_FEE: f64 = 0.2; @@ -84,11 +84,11 @@ impl PositionStrategy for TrailingStop { &self, position: Position, manager: &PositionManager, - ) -> (Position, Vec, Option) { + ) -> (Position, Option>, Option>) { let mut signals = vec![]; - let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE); let events = vec![]; - let mut order = None; + + let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE); let state = { if pl_perc > TrailingStop::GOOD_PROFIT_PERC { @@ -114,10 +114,20 @@ impl PositionStrategy for TrailingStop { match opt_pre_pw { Some(prev) => { if prev.profit_state() == Some(state) { - return (new_position, events, order); + return ( + new_position, + (!events.is_empty()).then_some(events), + (!signals.is_empty()).then_some(signals), + ); } } - None => return (new_position, events, order), + None => { + return ( + new_position, + (!events.is_empty()).then_some(events), + (!signals.is_empty()).then_some(signals), + ) + } }; let events = { @@ -158,7 +168,11 @@ impl PositionStrategy for TrailingStop { events }; - return (new_position, events, order); + return ( + new_position, + (!events.is_empty()).then_some(events), + (!signals.is_empty()).then_some(signals), + ); } } -- 2.47.2 From 3512dce35bdf3948b9f26f3d12850889a95d3de6 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 18 Jan 2021 00:01:15 +0000 Subject: [PATCH 058/127] pair managers implemented --- rustybot/src/managers.rs | 72 +++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 014a2a8..ca066af 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -230,6 +230,8 @@ impl PositionManager { } pub async fn update(&mut self, tick: u64) -> Result { + debug!("Updating {}", self.pair); + let opt_active_positions = self.client.active_positions(&self.pair).await?; let mut events = vec![]; @@ -431,44 +433,52 @@ impl OrderManager { } } +pub struct PairManager { + pair: SymbolPair, + price_manager: PriceManagerHandle, + order_manager: OrderManagerHandle, + position_manager: PositionManagerHandle, + dispatcher: Dispatcher, +} + +impl PairManager { + pub fn new(pair: SymbolPair, client: Client) -> Self { + Self { + pair: pair.clone(), + price_manager: PriceManagerHandle::new(pair.clone(), client.clone()), + order_manager: OrderManagerHandle::new( + pair.clone(), + client.clone(), + Box::new(FastOrderStrategy {}), + ), + position_manager: PositionManagerHandle::new( + pair.clone(), + client.clone(), + Box::new(TrailingStop::new()), + ), + dispatcher: Dispatcher::new(), + } + } +} + pub struct ExchangeManager { kind: ExchangeKind, - price_managers: Vec, - order_managers: Vec, - position_managers: Vec, - dispatcher: Dispatcher, + pair_managers: Vec, client: Client, } impl ExchangeManager { pub fn new(kind: &ExchangeKind, pairs: &Vec) -> Self { let client = Client::new(kind); + let pair_managers = pairs + .into_iter() + .map(|x| PairManager::new(x.clone(), client.clone())) + .collect(); - let mut position_managers = Vec::new(); - let mut order_managers = Vec::new(); - let mut price_managers = Vec::new(); - - for p in pairs { - position_managers.push(PositionManagerHandle::new( - p.clone(), - client.clone(), - Box::new(TrailingStop::new()), - )); - order_managers.push(OrderManagerHandle::new( - p.clone(), - client.clone(), - Box::new(FastOrderStrategy {}), - )); - price_managers.push(PriceManagerHandle::new(p.clone(), client.clone())); - } - - ExchangeManager { + Self { kind: kind.clone(), - position_managers, - order_managers, - price_managers, + pair_managers, client, - dispatcher: Dispatcher::new(), } } @@ -483,9 +493,9 @@ impl ExchangeManager { async fn update_position_managers(&mut self, tick: u64) -> Result { let mut futures: FuturesUnordered<_> = self - .position_managers + .pair_managers .iter_mut() - .map(|x| x.update(tick)) + .map(|x| x.position_manager.update(tick)) .collect(); while let Some(x) = futures.next().await {} @@ -495,9 +505,9 @@ impl ExchangeManager { async fn update_price_managers(&mut self, tick: u64) -> Result { let mut futures: FuturesUnordered<_> = self - .price_managers + .pair_managers .iter_mut() - .map(|x| x.update(tick)) + .map(|x| x.price_manager.update(tick)) .collect(); while let Some(x) = futures.next().await {} -- 2.47.2 From 8283ecde6099c28f7af14377748d9905dd4c0ead Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 18 Jan 2021 11:54:40 +0000 Subject: [PATCH 059/127] refactored SignalKind into Message and ActorMessage --- rustybot/src/events.rs | 241 ++++++++++++++++++++------------------- rustybot/src/managers.rs | 118 ++++++++++++------- rustybot/src/strategy.rs | 11 +- 3 files changed, 208 insertions(+), 162 deletions(-) diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index e2064bb..5ef4766 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -3,17 +3,24 @@ use std::future::Future; use tokio::task::JoinHandle; -use crate::managers::{OrderManager, PositionManager, PriceManager}; +use crate::managers::{OptionUpdate, OrderManager, PositionManager, PriceManager}; use crate::models::{Position, PositionProfitState}; +use tokio::sync::oneshot; -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub enum SignalKind { - Update(u64), - ClosePosition(Position), +#[derive(Debug)] +pub struct ActorMessage { + pub(crate) message: Message, + pub(crate) respond_to: oneshot::Sender, +} + +#[derive(Debug)] +pub enum Message { + Update { tick: u64 }, + ClosePosition { position: Position }, OpenPosition, } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct EventMetadata { position_id: Option, order_id: Option, @@ -44,7 +51,7 @@ pub enum EventKind { Any, } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct Event { kind: EventKind, tick: u64, @@ -74,113 +81,113 @@ impl Event { self.metadata } } - -pub struct Dispatcher { - event_handlers: HashMap JoinHandle<()>>>>, - profit_state_handlers: - HashMap JoinHandle<()>>>>, - signal_handlers: HashMap JoinHandle<()>>>>, - - on_any_event_handlers: Vec JoinHandle<()>>>, - on_any_profit_state_handlers: Vec JoinHandle<()>>>, -} - -impl Dispatcher { - pub fn new() -> Self { - Dispatcher { - event_handlers: HashMap::new(), - profit_state_handlers: HashMap::new(), - signal_handlers: HashMap::new(), - on_any_event_handlers: Vec::new(), - on_any_profit_state_handlers: Vec::new(), - } - } - - pub fn call_signal_handlers(&self, signal: &SignalKind) { - if let Some(functions) = self.signal_handlers.get(&signal) { - for f in functions { - f(signal); - } - } - } - - pub fn call_event_handlers(&self, event: &Event, status: &PriceManager) { - if let Some(functions) = self.event_handlers.get(&event.kind()) { - for f in functions { - f(event, status); - } - } - - for f in &self.on_any_event_handlers { - f(event, status); - } - } - - pub fn call_position_state_handlers(&self, position: &Position, status: &PriceManager) { - if let Some(profit_state) = &position.profit_state() { - if let Some(functions) = self.profit_state_handlers.get(profit_state) { - for f in functions { - f(position, status); - } - } - } - - for f in &self.on_any_profit_state_handlers { - f(position, status); - } - } - - pub fn register_event_handler(&mut self, event: EventKind, f: F) - where - F: Fn(&Event, &PriceManager) -> Fut, - Fut: Future + Send, - { - match event { - EventKind::Any => self - .on_any_event_handlers - .push(Box::new(move |e, s| tokio::spawn(f(&e, s)))), - _ => self - .event_handlers - .entry(event) - .or_default() - .push(Box::new(move |e, s| tokio::spawn(f(&e, s)))), - } - } - - pub fn register_positionstate_handler( - &mut self, - state: PositionProfitState, - f: F, - ) where - F: Fn(&Position, &PriceManager) -> Fut, - Fut: Future + Send, - { - match state { - // PositionProfitState::Any => self - // .on_any_position_state_handlers - // .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))), - _ => self - .profit_state_handlers - .entry(state) - .or_default() - .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))), - } - } - - pub fn register_signal_handler(&mut self, signal: SignalKind, f: F) - where - F: Fn(&SignalKind) -> Fut, - Fut: Future + Send, - { - match signal { - // PositionProfitState::Any => self - // .on_any_position_state_handlers - // .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))), - _ => self - .signal_handlers - .entry(signal) - .or_default() - .push(Box::new(move |s| tokio::spawn(f(s)))), - } - } -} +// +// pub struct Dispatcher { +// event_handlers: HashMap JoinHandle<()>>>>, +// profit_state_handlers: +// HashMap JoinHandle<()>>>>, +// signal_handlers: HashMap JoinHandle<()>>>>, +// +// on_any_event_handlers: Vec JoinHandle<()>>>, +// on_any_profit_state_handlers: Vec JoinHandle<()>>>, +// } +// +// impl Dispatcher { +// pub fn new() -> Self { +// Dispatcher { +// event_handlers: HashMap::new(), +// profit_state_handlers: HashMap::new(), +// signal_handlers: HashMap::new(), +// on_any_event_handlers: Vec::new(), +// on_any_profit_state_handlers: Vec::new(), +// } +// } +// +// pub fn call_signal_handlers(&self, signal: &SignalKind) { +// if let Some(functions) = self.signal_handlers.get(&signal) { +// for f in functions { +// f(signal); +// } +// } +// } +// +// pub fn call_event_handlers(&self, event: &Event, status: &PriceManager) { +// if let Some(functions) = self.event_handlers.get(&event.kind()) { +// for f in functions { +// f(event, status); +// } +// } +// +// for f in &self.on_any_event_handlers { +// f(event, status); +// } +// } +// +// pub fn call_position_state_handlers(&self, position: &Position, status: &PriceManager) { +// if let Some(profit_state) = &position.profit_state() { +// if let Some(functions) = self.profit_state_handlers.get(profit_state) { +// for f in functions { +// f(position, status); +// } +// } +// } +// +// for f in &self.on_any_profit_state_handlers { +// f(position, status); +// } +// } +// +// pub fn register_event_handler(&mut self, event: EventKind, f: F) +// where +// F: Fn(&Event, &PriceManager) -> Fut, +// Fut: Future + Send, +// { +// match event { +// EventKind::Any => self +// .on_any_event_handlers +// .push(Box::new(move |e, s| tokio::spawn(f(&e, s)))), +// _ => self +// .event_handlers +// .entry(event) +// .or_default() +// .push(Box::new(move |e, s| tokio::spawn(f(&e, s)))), +// } +// } +// +// pub fn register_positionstate_handler( +// &mut self, +// state: PositionProfitState, +// f: F, +// ) where +// F: Fn(&Position, &PriceManager) -> Fut, +// Fut: Future + Send, +// { +// match state { +// // PositionProfitState::Any => self +// // .on_any_position_state_handlers +// // .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))), +// _ => self +// .profit_state_handlers +// .entry(state) +// .or_default() +// .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))), +// } +// } +// +// pub fn register_signal_handler(&mut self, signal: SignalKind, f: F) +// where +// F: Fn(&SignalKind) -> Fut, +// Fut: Future + Send, +// { +// match signal { +// // PositionProfitState::Any => self +// // .on_any_position_state_handlers +// // .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))), +// _ => self +// .signal_handlers +// .entry(signal) +// .or_default() +// .push(Box::new(move |s| tokio::spawn(f(s)))), +// } +// } +// } diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index ca066af..baa3de3 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -8,15 +8,16 @@ use log::{debug, error, info}; use tokio::signal::unix::Signal; use tokio::sync::mpsc::channel; use tokio::sync::mpsc::{Receiver, Sender}; +use tokio::sync::oneshot; use crate::connectors::{Client, ExchangeKind}; use crate::currency::SymbolPair; -use crate::events::{Dispatcher, Event, SignalKind}; +use crate::events::{ActorMessage, Event, Message}; use crate::models::{ExecutedOrder, OrderForm, OrderKind, Position, PriceTicker}; use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop}; use crate::BoxError; -type OptionUpdate = (Option>, Option>); +pub type OptionUpdate = (Option>, Option>); pub struct EventManager { events: Vec, @@ -28,14 +29,14 @@ pub struct EventManager { #[derive(Debug)] pub struct PriceManager { - receiver: Receiver, + receiver: Receiver, pair: SymbolPair, prices: Vec, client: Client, } pub struct PriceManagerHandle { - sender: Sender, + sender: Sender, } impl PriceManagerHandle { @@ -54,13 +55,22 @@ impl PriceManagerHandle { Self { sender } } - pub async fn update(&mut self, tick: u64) { - self.sender.send(SignalKind::Update(tick)).await.unwrap(); + pub async fn update(&mut self, tick: u64) -> Result { + let (send, recv) = oneshot::channel(); + + self.sender + .send(ActorMessage { + message: Message::Update { tick }, + respond_to: send, + }) + .await?; + + Ok(recv.await?) } } impl PriceManager { - pub fn new(receiver: Receiver, pair: SymbolPair, client: Client) -> Self { + pub fn new(receiver: Receiver, pair: SymbolPair, client: Client) -> Self { PriceManager { receiver, pair, @@ -69,9 +79,9 @@ impl PriceManager { } } - pub async fn handle_message(&mut self, message: SignalKind) -> Result<(), BoxError> { - match message { - SignalKind::Update(tick) => { + pub async fn handle_message(&mut self, message: ActorMessage) -> Result<(), BoxError> { + match message.message { + Message::Update { tick } => { let a = self.update(tick).await?; self.add_entry(a); } @@ -93,7 +103,7 @@ impl PriceManager { current_prices, self.pair.clone(), None, - None, + // None, )) } @@ -118,7 +128,7 @@ pub struct PriceEntry { pair: SymbolPair, price: PriceTicker, events: Option>, - signals: Option>, + // signals: Option>, } impl PriceEntry { @@ -127,14 +137,14 @@ impl PriceEntry { price: PriceTicker, pair: SymbolPair, events: Option>, - signals: Option>, + // signals: Option>, ) -> Self { PriceEntry { tick, pair, price, events, - signals, + // signals, } } @@ -150,9 +160,9 @@ impl PriceEntry { pub fn events(&self) -> &Option> { &self.events } - pub fn signals(&self) -> &Option> { - &self.signals - } + // pub fn signals(&self) -> &Option> { + // &self.signals + // } } /****************** @@ -160,7 +170,7 @@ impl PriceEntry { ******************/ pub struct PositionManagerHandle { - sender: Sender, + sender: Sender, } impl PositionManagerHandle { @@ -180,14 +190,23 @@ impl PositionManagerHandle { Self { sender } } - pub async fn update(&mut self, tick: u64) { - self.sender.send(SignalKind::Update(tick)).await.unwrap(); + pub async fn update(&mut self, tick: u64) -> Result { + let (send, recv) = oneshot::channel(); + + self.sender + .send(ActorMessage { + message: Message::Update { tick }, + respond_to: send, + }) + .await?; + + Ok(recv.await?) } } #[derive(Debug)] pub struct PositionManager { - receiver: Receiver, + receiver: Receiver, current_tick: u64, pair: SymbolPair, positions_history: HashMap, @@ -198,7 +217,7 @@ pub struct PositionManager { impl PositionManager { pub fn new( - receiver: Receiver, + receiver: Receiver, pair: SymbolPair, client: Client, strategy: Box, @@ -218,10 +237,12 @@ impl PositionManager { self.current_tick } - pub async fn handle_message(&mut self, msg: SignalKind) -> Result<(), BoxError> { - match msg { - SignalKind::Update(tick) => { - self.update(tick).await?; + pub async fn handle_message(&mut self, msg: ActorMessage) -> Result<(), BoxError> { + match msg.message { + Message::Update { tick } => { + let result = self.update(tick).await?; + + msg.respond_to.send(result); } _ => {} }; @@ -296,7 +317,7 @@ impl PositionManager { pub type TrackedPositionsMap = HashMap; pub struct OrderManagerHandle { - sender: Sender, + sender: Sender, } impl OrderManagerHandle { @@ -316,20 +337,35 @@ impl OrderManagerHandle { Self { sender } } - pub async fn update(&mut self, tick: u64) { - self.sender.send(SignalKind::Update(tick)).await.unwrap(); + pub async fn update(&mut self, tick: u64) -> Result { + let (send, recv) = oneshot::channel(); + + self.sender + .send(ActorMessage { + message: Message::Update { tick }, + respond_to: send, + }) + .await?; + + Ok(recv.await?) } - pub async fn close_position(&mut self, position: Position) { + pub async fn close_position(&mut self, position: Position) -> Result { + let (send, recv) = oneshot::channel(); + self.sender - .send(SignalKind::ClosePosition(position)) - .await - .unwrap(); + .send(ActorMessage { + message: Message::ClosePosition { position }, + respond_to: send, + }) + .await?; + + Ok(recv.await?) } } pub struct OrderManager { - receiver: Receiver, + receiver: Receiver, tracked_positions: TrackedPositionsMap, pair: SymbolPair, open_orders: Vec, @@ -341,7 +377,7 @@ impl OrderManager { const UNDERCUT_PERC: f64 = 0.005; pub fn new( - receiver: Receiver, + receiver: Receiver, pair: SymbolPair, client: Client, strategy: Box, @@ -356,12 +392,12 @@ impl OrderManager { } } - pub async fn handle_message(&mut self, msg: SignalKind) -> Result<(), BoxError> { - match msg { - SignalKind::Update(_) => { + pub async fn handle_message(&mut self, msg: ActorMessage) -> Result<(), BoxError> { + match msg.message { + Message::Update { .. } => { self.update(); } - SignalKind::ClosePosition(position) => self.close_position(&position).await?, + Message::ClosePosition { position, .. } => self.close_position(&position).await?, _ => {} }; @@ -438,7 +474,7 @@ pub struct PairManager { price_manager: PriceManagerHandle, order_manager: OrderManagerHandle, position_manager: PositionManagerHandle, - dispatcher: Dispatcher, + // dispatcher: Dispatcher, } impl PairManager { @@ -456,7 +492,7 @@ impl PairManager { client.clone(), Box::new(TrailingStop::new()), ), - dispatcher: Dispatcher::new(), + // dispatcher: Dispatcher::new(), } } } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index c1b27ff..14b9d38 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -3,9 +3,10 @@ use std::fmt::{Debug, Formatter}; use dyn_clone::DynClone; -use crate::events::{Event, EventKind, EventMetadata, SignalKind}; +use crate::events::{Event, EventKind, EventMetadata, Message}; use crate::managers::{OrderManager, PositionManager, TrackedPositionsMap}; use crate::models::{ExecutedOrder, OrderForm, Position, PositionProfitState}; +use tokio::sync::oneshot; /*************** * DEFINITIONS @@ -17,7 +18,7 @@ pub trait PositionStrategy: DynClone + Send { &self, position: Position, manager: &PositionManager, - ) -> (Position, Option>, Option>); + ) -> (Position, Option>, Option>); } impl Debug for dyn PositionStrategy { @@ -84,7 +85,7 @@ impl PositionStrategy for TrailingStop { &self, position: Position, manager: &PositionManager, - ) -> (Position, Option>, Option>) { + ) -> (Position, Option>, Option>) { let mut signals = vec![]; let events = vec![]; @@ -102,7 +103,9 @@ impl PositionStrategy for TrailingStop { } else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 { PositionProfitState::Loss } else { - signals.push(SignalKind::ClosePosition(position.clone())); + signals.push(Message::ClosePosition { + position: position.clone(), + }); PositionProfitState::Critical } }; -- 2.47.2 From 945f5f63c1637c986d7e07900b72ea8fc128970c Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Tue, 19 Jan 2021 21:29:02 +0000 Subject: [PATCH 060/127] implemented order book call for bitfinex connector. submit order stub working --- rustybot/src/connectors.rs | 66 +++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 8 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index c80a17c..d3ea78d 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -1,14 +1,17 @@ -use std::convert::TryInto; -use std::fmt::{Debug, Formatter}; -use std::sync::Arc; - use async_trait::async_trait; use bitfinex::api::Bitfinex; use bitfinex::orders::{ActiveOrder, OrderMeta}; use bitfinex::ticker::TradingPairTicker; +use log::debug; +use std::convert::TryInto; +use std::fmt::{Debug, Formatter}; +use std::sync::Arc; use crate::currency::SymbolPair; -use crate::models::{ExecutedOrder, OrderForm, OrderKind, Position, PositionState, PriceTicker}; +use crate::models::{ + ExecutedOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionState, + PriceTicker, +}; use crate::BoxError; #[derive(Eq, PartialEq, Hash, Clone, Debug)] @@ -57,6 +60,10 @@ impl Client { pub async fn submit_order(&self, order: OrderForm) -> Result { self.inner.submit_order(order).await } + + pub async fn order_book(&self, pair: &SymbolPair) -> Result { + self.inner.order_book(pair).await + } } #[async_trait] @@ -64,6 +71,7 @@ pub trait Connector: Send + Sync { fn name(&self) -> String; async fn active_positions(&self, pair: &SymbolPair) -> Result>, BoxError>; async fn current_prices(&self, pair: &SymbolPair) -> Result; + async fn order_book(&self, pair: &SymbolPair) -> Result; async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError>; async fn submit_order(&self, order: OrderForm) -> Result; } @@ -141,23 +149,65 @@ impl Connector for BitfinexConnector { } async fn submit_order(&self, order: OrderForm) -> Result { + // TODO: change trading pair formatting. awful. let order_form = match &self.affiliate_code { Some(affiliate_code) => bitfinex::orders::OrderForm::new( - order.pair().trading_repr(), + format!("t{}", self.format_trading_pair(order.pair())), *order.price(), *order.amount(), order.kind().into(), ) .with_meta(OrderMeta::new(affiliate_code.clone())), None => bitfinex::orders::OrderForm::new( - order.pair().trading_repr(), + format!("t{}", self.format_trading_pair(order.pair())), *order.price(), *order.amount(), order.kind().into(), ), }; - Ok(self.bfx.orders.submit_order(&order_form).await?.into()) + let response = self.bfx.orders.submit_order(&order_form).await?; + + Ok(ExecutedOrder { + id: 1, + group_id: None, + client_id: 0, + symbol: "".to_string(), + creation_timestamp: 0, + update_timestamp: 0, + amount: 0.0, + amount_original: 0.0, + order_type: "".to_string(), + previous_order_type: None, + flags: None, + order_status: None, + price: 0.0, + price_avg: 0.0, + price_trailing: None, + price_aux_limit: None, + notify: 0, + hidden: 0, + placed_id: None, + }) + } + + async fn order_book(&self, pair: &SymbolPair) -> Result { + let x = self + .bfx + .book + .trading_pair(self.format_trading_pair(&pair), "P0".into()) + .await?; + + let entries = x + .into_iter() + .map(|x| OrderBookEntry::Trading { + price: x.price, + count: x.count as u64, + amount: x.amount, + }) + .collect(); + + Ok(OrderBook::new(pair.clone()).with_entries(entries)) } } -- 2.47.2 From e6cb512a174a756308bcb0be8de11895ce95a32c Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Tue, 19 Jan 2021 21:30:01 +0000 Subject: [PATCH 061/127] implemented orderbook structs, modified algorithm to calculate best price when closing orders --- rustybot/Cargo.lock | 10 +++ rustybot/Cargo.toml | 3 +- rustybot/src/managers.rs | 158 +++++++++++++++++++++++++++------------ rustybot/src/models.rs | 82 +++++++++++++++++++- 4 files changed, 202 insertions(+), 51 deletions(-) diff --git a/rustybot/Cargo.lock b/rustybot/Cargo.lock index 92cc2d1..d1015ad 100644 --- a/rustybot/Cargo.lock +++ b/rustybot/Cargo.lock @@ -301,6 +301,15 @@ dependencies = [ "log 0.4.11", ] +[[package]] +name = "float-cmp" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1132,6 +1141,7 @@ dependencies = [ "chrono", "dyn-clone", "fern", + "float-cmp", "futures-util", "log 0.4.11", "regex", diff --git a/rustybot/Cargo.toml b/rustybot/Cargo.toml index eecc05d..fe7f9b2 100644 --- a/rustybot/Cargo.toml +++ b/rustybot/Cargo.toml @@ -17,4 +17,5 @@ dyn-clone = "1" log = "0.4" fern = {version = "0.6", features = ["colored"]} chrono = "0.4" -byteorder = "1" \ No newline at end of file +byteorder = "1" +float-cmp = "0.8" \ No newline at end of file diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index baa3de3..53ba11e 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -13,7 +13,7 @@ use tokio::sync::oneshot; use crate::connectors::{Client, ExchangeKind}; use crate::currency::SymbolPair; use crate::events::{ActorMessage, Event, Message}; -use crate::models::{ExecutedOrder, OrderForm, OrderKind, Position, PriceTicker}; +use crate::models::{ExecutedOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker}; use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop}; use crate::BoxError; @@ -88,6 +88,8 @@ impl PriceManager { _ => {} } + message.respond_to.send((None, None)); + Ok(()) } @@ -200,7 +202,9 @@ impl PositionManagerHandle { }) .await?; - Ok(recv.await?) + let response = recv.await?; + + Ok(response) } } @@ -251,16 +255,14 @@ impl PositionManager { } pub async fn update(&mut self, tick: u64) -> Result { - debug!("Updating {}", self.pair); + // debug!("Updating {}", self.pair); let opt_active_positions = self.client.active_positions(&self.pair).await?; - let mut events = vec![]; - self.current_tick = tick; // we assume there is only ONE active position per pair match opt_active_positions { - // no open positions, no events and no order forms returned + // no open positions, no events and no messages returned None => return Ok((None, None)), Some(positions) => { @@ -271,24 +273,25 @@ impl PositionManager { .next() { // no open positions for our pair, setting active position to none - None => self.active_position = None, + None => { + self.active_position = None; + return Ok((None, None)); + } // applying strategy to open position and saving into struct Some(position) => { - let (position_after_strategy, strategy_events, _) = + let (position_after_strategy, strategy_events, strategy_messages) = self.strategy.on_new_tick(position, &self); - events.extend(strategy_events); - self.positions_history .insert(self.current_tick(), position_after_strategy.clone()); self.active_position = Some(position_after_strategy); + + return Ok((strategy_events, strategy_messages)); } } } }; - - Ok((None, None)) } pub fn position_previous_tick(&self, id: u64, tick: Option) -> Option<&Position> { @@ -374,8 +377,6 @@ pub struct OrderManager { } impl OrderManager { - const UNDERCUT_PERC: f64 = 0.005; - pub fn new( receiver: Receiver, pair: SymbolPair, @@ -401,13 +402,15 @@ impl OrderManager { _ => {} }; + msg.respond_to.send((None, None)); + Ok(()) } pub async fn close_position(&mut self, position: &Position) -> Result<(), BoxError> { let open_order = self.tracked_positions.get(&position.id()); - info!("Closing position {}", position.id()); + info!("Closing position #{}", position.id()); // checking if the position has an open order. // If so, the strategy method is called, otherwise we open @@ -422,9 +425,10 @@ impl OrderManager { } None => { info!("Getting current prices..."); - let current_prices = self.client.current_prices(&self.pair).await?; + let order_book = self.client.order_book(&self.pair).await?; + info!("Calculating best closing price..."); - let closing_price = self.best_closing_price(&position, ¤t_prices)?; + let closing_price = self.best_closing_price(&position, &order_book)?; info!("Submitting order..."); // submitting order @@ -449,23 +453,41 @@ impl OrderManager { } pub fn update(&self) -> Result { - unimplemented!() + // TODO: implement me + Ok((None, None)) } pub fn best_closing_price( &self, position: &Position, - price_ticker: &TradingPairTicker, + order_book: &OrderBook, ) -> Result { + let ask = order_book.lowest_ask(); + let bid = order_book.highest_bid(); + let avg = (bid + ask) / 2.0; + let delta = (ask - bid) / 10.0; + let price = { - if position.is_short() { - price_ticker.ask + let intermediate_price = { + if position.is_short() { + bid + delta + } else { + ask - delta + } + }; + + if avg > 9999.0 { + if position.is_short() { + intermediate_price.ceil() + } else { + intermediate_price.floor() + } } else { - price_ticker.bid + intermediate_price } }; - Ok(price * (1.0 - OrderManager::UNDERCUT_PERC)) + Ok(price) } } @@ -492,9 +514,29 @@ impl PairManager { client.clone(), Box::new(TrailingStop::new()), ), - // dispatcher: Dispatcher::new(), } } + + pub async fn update_managers(&mut self, tick: u64) -> Result<(), BoxError> { + let (opt_price_events, opt_price_messages) = self.price_manager.update(tick).await?; + let (opt_pos_events, opt_pos_messages) = self.position_manager.update(tick).await?; + let (opt_order_events, opt_order_messages) = self.order_manager.update(tick).await?; + + // TODO: to move into Handler? + + if let Some(messages) = opt_pos_messages { + for m in messages { + match m { + Message::ClosePosition { position } => { + self.order_manager.close_position(position).await?; + } + _ => {} + } + } + } + + Ok(()) + } } pub struct ExchangeManager { @@ -518,36 +560,56 @@ impl ExchangeManager { } } - pub async fn update_managers(&mut self, tick: u64) -> Result { - let (price_opt_events, price_opt_signals) = self.update_price_managers(tick).await?; - let (pos_opt_events, pos_opt_signals) = self.update_position_managers(tick).await?; - - debug!("{:?}", pos_opt_signals); - - Ok((pos_opt_events, price_opt_signals)) - } - - async fn update_position_managers(&mut self, tick: u64) -> Result { + pub async fn update_managers(&mut self, tick: u64) -> Result<(), BoxError> { let mut futures: FuturesUnordered<_> = self .pair_managers .iter_mut() - .map(|x| x.position_manager.update(tick)) + .map(|x| x.update_managers(tick)) .collect(); - while let Some(x) = futures.next().await {} + // execute the futures + while let Some(_) = futures.next().await {} - Ok((None, None)) + Ok(()) } - async fn update_price_managers(&mut self, tick: u64) -> Result { - let mut futures: FuturesUnordered<_> = self - .pair_managers - .iter_mut() - .map(|x| x.price_manager.update(tick)) - .collect(); - - while let Some(x) = futures.next().await {} - - Ok((None, None)) - } + // async fn update_position_managers(&mut self, tick: u64) -> Result { + // let mut res_events = Vec::new(); + // let mut res_messages = Vec::new(); + // + // let mut futures: FuturesUnordered<_> = self + // .pair_managers + // .iter_mut() + // .map(|x| x.position_manager.update(tick)) + // .collect(); + // + // while let Some(future_result) = futures.next().await { + // let (opt_events, opt_messages) = future_result?; + // + // if let Some(events) = opt_events { + // res_events.extend(events); + // } + // + // if let Some(messages) = opt_messages { + // res_messages.extend(messages); + // } + // } + // + // Ok(( + // (!res_events.is_empty()).then_some(res_events), + // (!res_messages.is_empty()).then_some(res_messages), + // )) + // } + // + // async fn update_price_managers(&mut self, tick: u64) -> Result { + // let mut futures: FuturesUnordered<_> = self + // .pair_managers + // .iter_mut() + // .map(|x| x.price_manager.update(tick)) + // .collect(); + // + // while let Some(x) = futures.next().await {} + // + // Ok((None, None)) + // } } diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index e2b53cb..10fa256 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -1,10 +1,11 @@ use std::fmt::Display; +use std::hash::{Hash, Hasher}; use chrono::{DateTime, TimeZone}; use crate::currency::SymbolPair; use crate::BoxError; -use std::hash::{Hash, Hasher}; +use float_cmp::ApproxEq; /*************** * Prices @@ -28,6 +29,83 @@ pub struct PriceTicker { * Orders ***************/ +#[derive(Debug)] +pub enum OrderBookEntry { + Trading { + price: f64, + count: u64, + amount: f64, + }, + Funding { + rate: f64, + period: u64, + count: u64, + amount: f64, + }, +} + +#[derive(Debug)] +pub struct OrderBook { + pair: SymbolPair, + entries: Vec, +} + +impl OrderBook { + pub fn new(pair: SymbolPair) -> Self { + OrderBook { + pair, + entries: Vec::new(), + } + } + + pub fn with_entries(mut self, entries: Vec) -> Self { + self.entries = entries; + self + } + + // TODO: distinguish between trading and funding + pub fn bids(&self) -> Vec<&OrderBookEntry> { + self.entries + .iter() + .filter(|x| match x { + OrderBookEntry::Trading { amount, .. } => amount > &0.0, + OrderBookEntry::Funding { amount, .. } => amount < &0.0, + }) + .collect() + } + + // TODO: distinguish between trading and funding + pub fn asks(&self) -> Vec<&OrderBookEntry> { + self.entries + .iter() + .filter(|x| match x { + OrderBookEntry::Trading { amount, .. } => amount < &0.0, + OrderBookEntry::Funding { amount, .. } => amount > &0.0, + }) + .collect() + } + + pub fn highest_bid(&self) -> f64 { + self.bids() + .iter() + .map(|x| match x { + OrderBookEntry::Trading { price, .. } => price, + OrderBookEntry::Funding { rate, .. } => rate, + }) + .fold(f64::NEG_INFINITY, |a, &b| a.max(b)) + } + + pub fn lowest_ask(&self) -> f64 { + self.asks() + .iter() + .map(|x| match x { + OrderBookEntry::Trading { price, .. } => price, + OrderBookEntry::Funding { rate, .. } => rate, + }) + .fold(f64::INFINITY, |a, &b| a.min(b)) + } +} + #[derive(Clone, Debug)] pub struct ExecutedOrder { pub id: i64, @@ -74,7 +152,7 @@ pub enum OrderKind { ExchangeIoc, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct OrderForm { /// Order Type: LIMIT, EXCHANGE LIMIT, MARKET, EXCHANGE MARKET, /// STOP, EXCHANGE STOP, STOP LIMIT, EXCHANGE STOP LIMIT, -- 2.47.2 From 16a32cdce708e99fa30b012af6ad806a4321d7cf Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Tue, 19 Jan 2021 21:30:15 +0000 Subject: [PATCH 062/127] signals -> messages --- rustybot/src/strategy.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 14b9d38..e1963e4 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -1,8 +1,8 @@ +use dyn_clone::DynClone; +use log::debug; use std::collections::HashMap; use std::fmt::{Debug, Formatter}; -use dyn_clone::DynClone; - use crate::events::{Event, EventKind, EventMetadata, Message}; use crate::managers::{OrderManager, PositionManager, TrackedPositionsMap}; use crate::models::{ExecutedOrder, OrderForm, Position, PositionProfitState}; @@ -86,7 +86,7 @@ impl PositionStrategy for TrailingStop { position: Position, manager: &PositionManager, ) -> (Position, Option>, Option>) { - let mut signals = vec![]; + let mut messages = vec![]; let events = vec![]; let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE); @@ -103,7 +103,8 @@ impl PositionStrategy for TrailingStop { } else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 { PositionProfitState::Loss } else { - signals.push(Message::ClosePosition { + debug!("Inserting close position message..."); + messages.push(Message::ClosePosition { position: position.clone(), }); PositionProfitState::Critical @@ -120,7 +121,7 @@ impl PositionStrategy for TrailingStop { return ( new_position, (!events.is_empty()).then_some(events), - (!signals.is_empty()).then_some(signals), + (!messages.is_empty()).then_some(messages), ); } } @@ -128,7 +129,7 @@ impl PositionStrategy for TrailingStop { return ( new_position, (!events.is_empty()).then_some(events), - (!signals.is_empty()).then_some(signals), + (!messages.is_empty()).then_some(messages), ) } }; @@ -174,7 +175,7 @@ impl PositionStrategy for TrailingStop { return ( new_position, (!events.is_empty()).then_some(events), - (!signals.is_empty()).then_some(signals), + (!messages.is_empty()).then_some(messages), ); } } -- 2.47.2 From 2216910edbe4e9dd4d7a3d21e112bff79038e983 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Fri, 22 Jan 2021 15:37:53 +0000 Subject: [PATCH 063/127] ExecutedOrder -> ActiveOrder --- rustybot/src/connectors.rs | 27 +++++++++++++++------------ rustybot/src/managers.rs | 6 +++--- rustybot/src/models.rs | 4 ++-- rustybot/src/strategy.rs | 10 +++++----- 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index d3ea78d..9212b8d 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -1,6 +1,6 @@ use async_trait::async_trait; use bitfinex::api::Bitfinex; -use bitfinex::orders::{ActiveOrder, OrderMeta}; +use bitfinex::orders::OrderMeta; use bitfinex::ticker::TradingPairTicker; use log::debug; use std::convert::TryInto; @@ -9,7 +9,7 @@ use std::sync::Arc; use crate::currency::SymbolPair; use crate::models::{ - ExecutedOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionState, + ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionState, PriceTicker, }; use crate::BoxError; @@ -53,11 +53,11 @@ impl Client { self.inner.current_prices(pair).await } - pub async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError> { + pub async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError> { self.inner.active_orders(pair).await } - pub async fn submit_order(&self, order: OrderForm) -> Result { + pub async fn submit_order(&self, order: OrderForm) -> Result { self.inner.submit_order(order).await } @@ -72,8 +72,8 @@ pub trait Connector: Send + Sync { async fn active_positions(&self, pair: &SymbolPair) -> Result>, BoxError>; async fn current_prices(&self, pair: &SymbolPair) -> Result; async fn order_book(&self, pair: &SymbolPair) -> Result; - async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError>; - async fn submit_order(&self, order: OrderForm) -> Result; + async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError>; + async fn submit_order(&self, order: OrderForm) -> Result; } impl Debug for dyn Connector { @@ -144,11 +144,11 @@ impl Connector for BitfinexConnector { Ok(ticker) } - async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError> { + async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError> { unimplemented!() } - async fn submit_order(&self, order: OrderForm) -> Result { + async fn submit_order(&self, order: OrderForm) -> Result { // TODO: change trading pair formatting. awful. let order_form = match &self.affiliate_code { Some(affiliate_code) => bitfinex::orders::OrderForm::new( @@ -168,7 +168,7 @@ impl Connector for BitfinexConnector { let response = self.bfx.orders.submit_order(&order_form).await?; - Ok(ExecutedOrder { + Ok(ActiveOrder { id: 1, group_id: None, client_id: 0, @@ -195,7 +195,10 @@ impl Connector for BitfinexConnector { let x = self .bfx .book - .trading_pair(self.format_trading_pair(&pair), "P0".into()) + .trading_pair( + self.format_trading_pair(&pair), + bitfinex::book::BookPrecision::P0, + ) .await?; let entries = x @@ -295,8 +298,8 @@ impl From for PriceTicker { } } -impl From for ExecutedOrder { - fn from(o: ActiveOrder) -> Self { +impl From for ActiveOrder { + fn from(o: bitfinex::orders::ActiveOrder) -> Self { Self { id: o.id, group_id: o.group_id, diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 53ba11e..f02e196 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -13,7 +13,7 @@ use tokio::sync::oneshot; use crate::connectors::{Client, ExchangeKind}; use crate::currency::SymbolPair; use crate::events::{ActorMessage, Event, Message}; -use crate::models::{ExecutedOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker}; +use crate::models::{ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker}; use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop}; use crate::BoxError; @@ -317,7 +317,7 @@ impl PositionManager { * ORDERS ******************/ -pub type TrackedPositionsMap = HashMap; +pub type TrackedPositionsMap = HashMap; pub struct OrderManagerHandle { sender: Sender, @@ -371,7 +371,7 @@ pub struct OrderManager { receiver: Receiver, tracked_positions: TrackedPositionsMap, pair: SymbolPair, - open_orders: Vec, + open_orders: Vec, client: Client, strategy: Box, } diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index 10fa256..c8b989a 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -107,7 +107,7 @@ impl OrderBook { } #[derive(Clone, Debug)] -pub struct ExecutedOrder { +pub struct ActiveOrder { pub id: i64, pub group_id: Option, pub client_id: i64, @@ -129,7 +129,7 @@ pub struct ExecutedOrder { pub placed_id: Option, } -impl Hash for ExecutedOrder { +impl Hash for ActiveOrder { fn hash(&self, state: &mut H) { state.write(&self.id.to_le_bytes()) } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index e1963e4..0ca1981 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -5,7 +5,7 @@ use std::fmt::{Debug, Formatter}; use crate::events::{Event, EventKind, EventMetadata, Message}; use crate::managers::{OrderManager, PositionManager, TrackedPositionsMap}; -use crate::models::{ExecutedOrder, OrderForm, Position, PositionProfitState}; +use crate::models::{ActiveOrder, OrderForm, Position, PositionProfitState}; use tokio::sync::oneshot; /*************** @@ -37,8 +37,8 @@ pub trait OrderStrategy: DynClone + Send { /// a position that has an open order associated to it. fn on_position_close( &self, - order: &ExecutedOrder, - tracked_positions: &HashMap, + order: &ActiveOrder, + tracked_positions: &HashMap, ) -> TrackedPositionsMap; } @@ -194,8 +194,8 @@ impl OrderStrategy for FastOrderStrategy { fn on_position_close( &self, - order: &ExecutedOrder, - tracked_positions: &HashMap, + order: &ActiveOrder, + tracked_positions: &HashMap, ) -> TrackedPositionsMap { unimplemented!() } -- 2.47.2 From 85c02e4053e365fa15b8f82a724e6df719bb1fd3 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Fri, 22 Jan 2021 16:08:03 +0000 Subject: [PATCH 064/127] TryFrom<&str> to FromStr for SymbolPair --- rustybot/src/currency.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rustybot/src/currency.rs b/rustybot/src/currency.rs index 87f02be..ae4a618 100644 --- a/rustybot/src/currency.rs +++ b/rustybot/src/currency.rs @@ -6,6 +6,7 @@ use std::fmt::{Display, Formatter}; use regex::Regex; use crate::BoxError; +use std::str::FromStr; #[derive(Clone, PartialEq, Hash, Debug, Eq)] pub struct Symbol { @@ -85,10 +86,10 @@ impl Into for SymbolPair { } } -impl TryFrom<&str> for SymbolPair { - type Error = BoxError; +impl FromStr for SymbolPair { + type Err = BoxError; - fn try_from(value: &str) -> Result { + fn from_str(value: &str) -> Result { const REGEX: &str = r"^[t|f](?P\w{3,7}):?(?P\w{3,7})"; let captures = Regex::new(REGEX)?.captures(&value).ok_or("Invalid input")?; -- 2.47.2 From 191a21bec9a390a9704e887ac3544a3527b148a2 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Fri, 22 Jan 2021 16:08:40 +0000 Subject: [PATCH 065/127] visible only in crate --- rustybot/src/models.rs | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index c8b989a..a4b04f7 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -2,10 +2,10 @@ use std::fmt::Display; use std::hash::{Hash, Hasher}; use chrono::{DateTime, TimeZone}; +use float_cmp::ApproxEq; use crate::currency::SymbolPair; use crate::BoxError; -use float_cmp::ApproxEq; /*************** * Prices @@ -108,25 +108,19 @@ impl OrderBook { #[derive(Clone, Debug)] pub struct ActiveOrder { - pub id: i64, - pub group_id: Option, - pub client_id: i64, - pub symbol: String, - pub creation_timestamp: i64, - pub update_timestamp: i64, - pub amount: f64, - pub amount_original: f64, - pub order_type: String, - pub previous_order_type: Option, - pub flags: Option, - pub order_status: Option, - pub price: f64, - pub price_avg: f64, - pub price_trailing: Option, - pub price_aux_limit: Option, - pub notify: i32, - pub hidden: i32, - pub placed_id: Option, + pub(crate) id: u64, + pub(crate) group_id: Option, + pub(crate) client_id: u64, + pub(crate) symbol: SymbolPair, + pub(crate) creation_timestamp: u64, + pub(crate) update_timestamp: u64, + pub(crate) amount: f64, + pub(crate) amount_original: f64, + pub(crate) order_type: OrderKind, + pub(crate) previous_order_type: Option, + pub(crate) price: f64, + pub(crate) price_avg: f64, + pub(crate) hidden: bool, } impl Hash for ActiveOrder { -- 2.47.2 From b5b3455f08f6a17e7d6036860975b9ff50834898 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Fri, 22 Jan 2021 16:09:17 +0000 Subject: [PATCH 066/127] implemented OrderKind <-> bitfinex::OrderKind, returning proper response from submit order --- rustybot/src/connectors.rs | 81 ++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 46 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 9212b8d..d708ddc 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -13,6 +13,7 @@ use crate::models::{ PriceTicker, }; use crate::BoxError; +use std::str::FromStr; #[derive(Eq, PartialEq, Hash, Clone, Debug)] pub enum ExchangeKind { @@ -169,25 +170,19 @@ impl Connector for BitfinexConnector { let response = self.bfx.orders.submit_order(&order_form).await?; Ok(ActiveOrder { - id: 1, - group_id: None, - client_id: 0, - symbol: "".to_string(), - creation_timestamp: 0, - update_timestamp: 0, - amount: 0.0, - amount_original: 0.0, - order_type: "".to_string(), - previous_order_type: None, - flags: None, - order_status: None, - price: 0.0, - price_avg: 0.0, - price_trailing: None, - price_aux_limit: None, - notify: 0, - hidden: 0, - placed_id: None, + id: response.id(), + group_id: response.gid(), + client_id: response.cid(), + symbol: SymbolPair::from_str(response.symbol())?, + creation_timestamp: response.mts_create(), + update_timestamp: response.mts_update(), + amount: response.amount(), + amount_original: response.amount_orig(), + order_type: (&response.order_type()).into(), + previous_order_type: response.prev_order_type().map(|x| (&x).into()), + price: response.price(), + price_avg: response.price_avg(), + hidden: response.hidden(), }) } @@ -227,7 +222,7 @@ impl TryInto for bitfinex::positions::Position { }; Ok(Position::new( - self.symbol().try_into()?, + SymbolPair::from_str(self.symbol())?, state, self.amount(), self.base_price(), @@ -261,6 +256,26 @@ impl From<&OrderKind> for bitfinex::orders::OrderKind { } } +impl From<&bitfinex::orders::OrderKind> for OrderKind { + fn from(o: &bitfinex::orders::OrderKind) -> Self { + match o { + bitfinex::orders::OrderKind::Limit => OrderKind::Limit, + bitfinex::orders::OrderKind::ExchangeLimit => OrderKind::ExchangeLimit, + bitfinex::orders::OrderKind::Market => OrderKind::Market, + bitfinex::orders::OrderKind::ExchangeMarket => OrderKind::ExchangeMarket, + bitfinex::orders::OrderKind::Stop => OrderKind::Stop, + bitfinex::orders::OrderKind::ExchangeStop => OrderKind::ExchangeStop, + bitfinex::orders::OrderKind::StopLimit => OrderKind::StopLimit, + bitfinex::orders::OrderKind::ExchangeStopLimit => OrderKind::ExchangeStopLimit, + bitfinex::orders::OrderKind::TrailingStop => OrderKind::TrailingStop, + bitfinex::orders::OrderKind::Fok => OrderKind::Fok, + bitfinex::orders::OrderKind::ExchangeFok => OrderKind::ExchangeFok, + bitfinex::orders::OrderKind::Ioc => OrderKind::Ioc, + bitfinex::orders::OrderKind::ExchangeIoc => OrderKind::ExchangeIoc, + } + } +} + impl From for bitfinex::orders::OrderKind { fn from(o: OrderKind) -> Self { match o { @@ -297,29 +312,3 @@ impl From for PriceTicker { } } } - -impl From for ActiveOrder { - fn from(o: bitfinex::orders::ActiveOrder) -> Self { - Self { - id: o.id, - group_id: o.group_id, - client_id: o.client_id, - symbol: o.symbol, - creation_timestamp: o.creation_timestamp, - update_timestamp: o.update_timestamp, - amount: o.amount, - amount_original: o.amount_original, - order_type: o.order_type, - previous_order_type: o.previous_order_type, - flags: o.flags, - order_status: o.order_status, - price: o.price, - price_avg: o.price_avg, - price_trailing: o.price_trailing, - price_aux_limit: o.price_aux_limit, - notify: o.notify, - hidden: o.hidden, - placed_id: o.placed_id, - } - } -} -- 2.47.2 From 47f17efcfb3ed8d0f8937fdf06d5809136ee05a8 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sat, 23 Jan 2021 11:44:59 +0000 Subject: [PATCH 067/127] impl PartialEq for ActiveOrder --- rustybot/src/models.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index a4b04f7..5c89a44 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -129,6 +129,14 @@ impl Hash for ActiveOrder { } } +impl PartialEq for ActiveOrder { + fn eq(&self, other: &Self) -> bool { + self.id == other.id && self.client_id == other.client_id && self.group_id == other.group_id + } +} + +impl Eq for ActiveOrder {} + #[derive(Copy, Clone, Debug, Hash)] pub enum OrderKind { Limit, -- 2.47.2 From a1354c2862f9e1ea79663e100c03ad79fbe90b66 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sat, 23 Jan 2021 11:46:39 +0000 Subject: [PATCH 068/127] implemented Default for FastOrderStrategy. FastOrderStrategy closes position with a Market order if threshold is overridden --- rustybot/src/events.rs | 11 ++- rustybot/src/managers.rs | 194 ++++++++++++++++++++++++++++++--------- rustybot/src/strategy.rs | 70 ++++++++++++-- 3 files changed, 217 insertions(+), 58 deletions(-) diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 5ef4766..44e57d6 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -4,7 +4,7 @@ use std::future::Future; use tokio::task::JoinHandle; use crate::managers::{OptionUpdate, OrderManager, PositionManager, PriceManager}; -use crate::models::{Position, PositionProfitState}; +use crate::models::{OrderKind, Position, PositionProfitState}; use tokio::sync::oneshot; #[derive(Debug)] @@ -15,8 +15,13 @@ pub struct ActorMessage { #[derive(Debug)] pub enum Message { - Update { tick: u64 }, - ClosePosition { position: Position }, + Update { + tick: u64, + }, + ClosePosition { + position: Position, + order_kind: OrderKind, + }, OpenPosition, } diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index f02e196..dbbc0d6 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -353,12 +353,19 @@ impl OrderManagerHandle { Ok(recv.await?) } - pub async fn close_position(&mut self, position: Position) -> Result { + pub async fn close_position( + &mut self, + position: Position, + order_kind: OrderKind, + ) -> Result { let (send, recv) = oneshot::channel(); self.sender .send(ActorMessage { - message: Message::ClosePosition { position }, + message: Message::ClosePosition { + position, + order_kind, + }, respond_to: send, }) .await?; @@ -398,7 +405,10 @@ impl OrderManager { Message::Update { .. } => { self.update(); } - Message::ClosePosition { position, .. } => self.close_position(&position).await?, + Message::ClosePosition { + position, + order_kind, + } => self.close_position(&position, &order_kind).await?, _ => {} }; @@ -407,87 +417,177 @@ impl OrderManager { Ok(()) } - pub async fn close_position(&mut self, position: &Position) -> Result<(), BoxError> { + pub async fn close_position( + &mut self, + position: &Position, + order_kind: &OrderKind, + ) -> Result<(), BoxError> { let open_order = self.tracked_positions.get(&position.id()); - info!("Closing position #{}", position.id()); + debug!("Closing position #{}", position.id()); + + debug!("Getting current prices..."); + let order_book = self.client.order_book(&self.pair).await?; // checking if the position has an open order. // If so, the strategy method is called, otherwise we open // an undercut limit order at the best current price. match open_order { Some(open_order) => { - info!("There is an open order. Calling strategy."); + debug!("There is an open order. Calling strategy."); + let (_, messages) = + self.strategy + .on_position_close(open_order, position, &order_book)?; - self.tracked_positions = self - .strategy - .on_position_close(open_order, &self.tracked_positions); + if let Some(messages) = messages { + for m in messages { + match m { + Message::ClosePosition { + position, + order_kind, + } => { + // TODO FIXME + match order_kind { + OrderKind::Market => { + self.submit_market_order(1.0, position.amount().neg()) + .await?; + } + _ => {} + } + } + _ => { + debug!("Received unsupported message from order strategy. Unimplemented.") + } + } + } + } } None => { - info!("Getting current prices..."); - let order_book = self.client.order_book(&self.pair).await?; + let closing_price = self.best_closing_price(&position, &order_book); - info!("Calculating best closing price..."); - let closing_price = self.best_closing_price(&position, &order_book)?; - - info!("Submitting order..."); - // submitting order - let order_form = OrderForm::new( - &self.pair, - closing_price, - position.amount().neg(), - OrderKind::Limit, - ); - - match self.client.submit_order(order_form).await { - Err(e) => error!("Could not submit order: {}", e), - Ok(o) => { - self.tracked_positions.insert(position.id(), o); - info!("Done!"); + let active_order = match order_kind { + OrderKind::Limit => { + self.submit_limit_order(closing_price, position.amount().neg()) + .await? + } + OrderKind::ExchangeLimit => { + self.submit_exchange_limit_order(closing_price, position.amount().neg()) + .await? + } + OrderKind::Market => { + self.submit_market_order(closing_price, position.amount().neg()) + .await? + } + OrderKind::ExchangeMarket => { + self.submit_exchange_market_order(closing_price, position.amount().neg()) + .await? + } + _ => { + unimplemented!() } }; + + self.tracked_positions.insert(position.id(), active_order); } } Ok(()) } + pub async fn submit_limit_order( + &mut self, + closing_price: f64, + amount: f64, + ) -> Result { + info!( + "Submitting exchange limit order of {} {}...", + amount, + self.pair.base() + ); + let order_form = OrderForm::new(&self.pair, closing_price, amount, OrderKind::Limit); + + Ok(self.client.submit_order(order_form).await?) + } + + pub async fn submit_exchange_limit_order( + &mut self, + closing_price: f64, + amount: f64, + ) -> Result { + info!( + "Submitting exchange limit order of {} {}...", + amount, + self.pair.base() + ); + let order_form = + OrderForm::new(&self.pair, closing_price, amount, OrderKind::ExchangeLimit); + + Ok(self.client.submit_order(order_form).await?) + } + + pub async fn submit_market_order( + &mut self, + closing_price: f64, + amount: f64, + ) -> Result { + info!( + "Submitting market order of {} {}...", + amount, + self.pair.base() + ); + let order_form = OrderForm::new(&self.pair, closing_price, amount, OrderKind::Market); + + Ok(self.client.submit_order(order_form).await?) + } + + pub async fn submit_exchange_market_order( + &mut self, + closing_price: f64, + amount: f64, + ) -> Result { + info!( + "Submitting market order of {} {}...", + amount, + self.pair.base() + ); + let order_form = + OrderForm::new(&self.pair, closing_price, amount, OrderKind::ExchangeMarket); + + Ok(self.client.submit_order(order_form).await?) + } + pub fn update(&self) -> Result { // TODO: implement me Ok((None, None)) } - pub fn best_closing_price( - &self, - position: &Position, - order_book: &OrderBook, - ) -> Result { + pub fn best_closing_price(&self, position: &Position, order_book: &OrderBook) -> f64 { let ask = order_book.lowest_ask(); let bid = order_book.highest_bid(); let avg = (bid + ask) / 2.0; let delta = (ask - bid) / 10.0; - let price = { - let intermediate_price = { + let closing_price = { + let closing_price = { if position.is_short() { - bid + delta + bid - delta } else { - ask - delta + ask + delta } }; if avg > 9999.0 { if position.is_short() { - intermediate_price.ceil() + closing_price.ceil() } else { - intermediate_price.floor() + closing_price.floor() } } else { - intermediate_price + closing_price } }; - Ok(price) + closing_price } } @@ -496,7 +596,6 @@ pub struct PairManager { price_manager: PriceManagerHandle, order_manager: OrderManagerHandle, position_manager: PositionManagerHandle, - // dispatcher: Dispatcher, } impl PairManager { @@ -507,7 +606,7 @@ impl PairManager { order_manager: OrderManagerHandle::new( pair.clone(), client.clone(), - Box::new(FastOrderStrategy {}), + Box::new(FastOrderStrategy::default()), ), position_manager: PositionManagerHandle::new( pair.clone(), @@ -527,8 +626,13 @@ impl PairManager { if let Some(messages) = opt_pos_messages { for m in messages { match m { - Message::ClosePosition { position } => { - self.order_manager.close_position(position).await?; + Message::ClosePosition { + position, + order_kind, + } => { + self.order_manager + .close_position(position, order_kind) + .await?; } _ => {} } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 0ca1981..985a4ed 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -1,12 +1,16 @@ -use dyn_clone::DynClone; -use log::debug; use std::collections::HashMap; use std::fmt::{Debug, Formatter}; +use dyn_clone::DynClone; +use log::{debug, info}; +use tokio::sync::oneshot; + use crate::events::{Event, EventKind, EventMetadata, Message}; use crate::managers::{OrderManager, PositionManager, TrackedPositionsMap}; -use crate::models::{ActiveOrder, OrderForm, Position, PositionProfitState}; -use tokio::sync::oneshot; +use crate::models::{ + ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionProfitState, +}; +use crate::BoxError; /*************** * DEFINITIONS @@ -38,8 +42,9 @@ pub trait OrderStrategy: DynClone + Send { fn on_position_close( &self, order: &ActiveOrder, - tracked_positions: &HashMap, - ) -> TrackedPositionsMap; + open_position: &Position, + order_book: &OrderBook, + ) -> Result<(Option>, Option>), BoxError>; } impl Debug for dyn OrderStrategy { @@ -106,6 +111,7 @@ impl PositionStrategy for TrailingStop { debug!("Inserting close position message..."); messages.push(Message::ClosePosition { position: position.clone(), + order_kind: OrderKind::Limit, }); PositionProfitState::Critical } @@ -181,7 +187,23 @@ impl PositionStrategy for TrailingStop { } #[derive(Clone, Debug)] -pub struct FastOrderStrategy {} +pub struct FastOrderStrategy { + // threshold (%) for which we trigger a market order + // to close an open position + threshold: f64, +} + +impl Default for FastOrderStrategy { + fn default() -> Self { + Self { threshold: 0.2 } + } +} + +impl FastOrderStrategy { + pub fn new(threshold: f64) -> Self { + Self { threshold } + } +} impl OrderStrategy for FastOrderStrategy { fn name(&self) -> String { @@ -195,8 +217,36 @@ impl OrderStrategy for FastOrderStrategy { fn on_position_close( &self, order: &ActiveOrder, - tracked_positions: &HashMap, - ) -> TrackedPositionsMap { - unimplemented!() + active_position: &Position, + order_book: &OrderBook, + ) -> Result<(Option>, Option>), BoxError> { + let mut messages = vec![]; + + // long + let offer_comparison = { + if order.amount > 0.0 { + order_book.highest_bid() + } else { + order_book.lowest_ask() + } + }; + + // if the best offer is higher than our threshold, + // ask the manager to close the position with a market order + let delta = (1.0 - (offer_comparison / order.price)) * 100.0; + + debug!( + "Offer comp: {} | Our offer: {} | Current delta: {}", + offer_comparison, order.price, delta + ); + + if delta > self.threshold { + messages.push(Message::ClosePosition { + position: active_position.clone(), + order_kind: OrderKind::Market, + }) + } + + Ok((None, (!messages.is_empty()).then_some(messages))) } } -- 2.47.2 From a1d905ebea1f72c5a8ad1115f376a07589478d54 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sat, 23 Jan 2021 13:44:08 +0000 Subject: [PATCH 069/127] order closing working --- rustybot/src/connectors.rs | 70 +++++++++++++++++++++++++++----------- rustybot/src/managers.rs | 19 +++++++---- rustybot/src/strategy.rs | 9 ++--- 3 files changed, 65 insertions(+), 33 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index d708ddc..b3702af 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -1,11 +1,14 @@ +use std::convert::{TryFrom, TryInto}; +use std::fmt::{Debug, Formatter}; +use std::str::FromStr; +use std::sync::Arc; + use async_trait::async_trait; use bitfinex::api::Bitfinex; -use bitfinex::orders::OrderMeta; +use bitfinex::orders::{OrderMeta, OrderResponse}; use bitfinex::ticker::TradingPairTicker; +use chrono::{DateTime, NaiveDate, NaiveDateTime, Utc}; use log::debug; -use std::convert::TryInto; -use std::fmt::{Debug, Formatter}; -use std::sync::Arc; use crate::currency::SymbolPair; use crate::models::{ @@ -13,7 +16,6 @@ use crate::models::{ PriceTicker, }; use crate::BoxError; -use std::str::FromStr; #[derive(Eq, PartialEq, Hash, Clone, Debug)] pub enum ExchangeKind { @@ -65,6 +67,10 @@ impl Client { pub async fn order_book(&self, pair: &SymbolPair) -> Result { self.inner.order_book(pair).await } + + pub async fn cancel_order(&self, order: &ActiveOrder) -> Result { + self.inner.cancel_order(order).await + } } #[async_trait] @@ -75,6 +81,7 @@ pub trait Connector: Send + Sync { async fn order_book(&self, pair: &SymbolPair) -> Result; async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError>; async fn submit_order(&self, order: OrderForm) -> Result; + async fn cancel_order(&self, order: &ActiveOrder) -> Result; } impl Debug for dyn Connector { @@ -169,21 +176,7 @@ impl Connector for BitfinexConnector { let response = self.bfx.orders.submit_order(&order_form).await?; - Ok(ActiveOrder { - id: response.id(), - group_id: response.gid(), - client_id: response.cid(), - symbol: SymbolPair::from_str(response.symbol())?, - creation_timestamp: response.mts_create(), - update_timestamp: response.mts_update(), - amount: response.amount(), - amount_original: response.amount_orig(), - order_type: (&response.order_type()).into(), - previous_order_type: response.prev_order_type().map(|x| (&x).into()), - price: response.price(), - price_avg: response.price_avg(), - hidden: response.hidden(), - }) + Ok(response.try_into()?) } async fn order_book(&self, pair: &SymbolPair) -> Result { @@ -207,6 +200,43 @@ impl Connector for BitfinexConnector { Ok(OrderBook::new(pair.clone()).with_entries(entries)) } + + async fn cancel_order(&self, order: &ActiveOrder) -> Result { + let date = DateTime::::from_utc( + NaiveDateTime::from_timestamp(order.update_timestamp as i64, 0), + Utc, + ); + let cancel_form = bitfinex::orders::CancelOrderForm::new(order.id, order.client_id, date); + + Ok(self + .bfx + .orders + .cancel_order(&cancel_form) + .await? + .try_into()?) + } +} + +impl TryFrom for ActiveOrder { + type Error = BoxError; + + fn try_from(response: OrderResponse) -> Result { + Ok(Self { + id: response.id(), + group_id: response.gid(), + client_id: response.cid(), + symbol: SymbolPair::from_str(response.symbol())?, + creation_timestamp: response.mts_create(), + update_timestamp: response.mts_update(), + amount: response.amount(), + amount_original: response.amount_orig(), + order_type: (&response.order_type()).into(), + previous_order_type: response.prev_order_type().map(|x| (&x).into()), + price: response.price(), + price_avg: response.price_avg(), + hidden: response.hidden(), + }) + } } impl TryInto for bitfinex::positions::Position { diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index dbbc0d6..cdc9d7e 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -449,8 +449,15 @@ impl OrderManager { // TODO FIXME match order_kind { OrderKind::Market => { - self.submit_market_order(1.0, position.amount().neg()) - .await?; + info!("Closing open order with a market order."); + if let Ok(_) = self + .submit_market_order(1.0, position.amount().neg()) + .await + { + // cancel active order + info!("Cancelling open order #{}", open_order.id); + self.client.cancel_order(open_order).await?; + } } _ => {} } @@ -495,7 +502,7 @@ impl OrderManager { } pub async fn submit_limit_order( - &mut self, + &self, closing_price: f64, amount: f64, ) -> Result { @@ -510,7 +517,7 @@ impl OrderManager { } pub async fn submit_exchange_limit_order( - &mut self, + &self, closing_price: f64, amount: f64, ) -> Result { @@ -526,7 +533,7 @@ impl OrderManager { } pub async fn submit_market_order( - &mut self, + &self, closing_price: f64, amount: f64, ) -> Result { @@ -541,7 +548,7 @@ impl OrderManager { } pub async fn submit_exchange_market_order( - &mut self, + &self, closing_price: f64, amount: f64, ) -> Result { diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 985a4ed..b954bd8 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -16,7 +16,7 @@ use crate::BoxError; * DEFINITIONS ***************/ -pub trait PositionStrategy: DynClone + Send { +pub trait PositionStrategy: DynClone + Send + Sync { fn name(&self) -> String; fn on_new_tick( &self, @@ -31,7 +31,7 @@ impl Debug for dyn PositionStrategy { } } -pub trait OrderStrategy: DynClone + Send { +pub trait OrderStrategy: DynClone + Send + Sync { /// The name of the strategy, used for debugging purposes fn name(&self) -> String; /// This method is called when the OrderManager checks the open orders on a new tick. @@ -235,11 +235,6 @@ impl OrderStrategy for FastOrderStrategy { // ask the manager to close the position with a market order let delta = (1.0 - (offer_comparison / order.price)) * 100.0; - debug!( - "Offer comp: {} | Our offer: {} | Current delta: {}", - offer_comparison, order.price, delta - ); - if delta > self.threshold { messages.push(Message::ClosePosition { position: active_position.clone(), -- 2.47.2 From 5b84c997032300824086131405958327796f3ec6 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sat, 23 Jan 2021 16:13:37 +0000 Subject: [PATCH 070/127] internal order modeling overhaul --- rustybot/src/bot.rs | 4 +- rustybot/src/connectors.rs | 256 +++++++++++++++++++-------------- rustybot/src/events.rs | 4 +- rustybot/src/managers.rs | 143 ++++--------------- rustybot/src/models.rs | 284 +++++++++++++++++++++++-------------- rustybot/src/strategy.rs | 29 +++- 6 files changed, 386 insertions(+), 334 deletions(-) diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index c8f84dd..b332147 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use log::{debug, error, info}; use tokio::time::delay_for; -use crate::connectors::{Client, ExchangeKind}; +use crate::connectors::{Client, ExchangeDetails}; use crate::currency::{Symbol, SymbolPair}; use crate::events::Event; use crate::managers::{ExchangeManager, OrderManager, PositionManager, PriceManager}; @@ -21,7 +21,7 @@ pub struct BfxBot { impl BfxBot { pub fn new( - exchanges: Vec, + exchanges: Vec, trading_symbols: Vec, quote: Symbol, tick_duration: Duration, diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index b3702af..06b4756 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use async_trait::async_trait; use bitfinex::api::Bitfinex; -use bitfinex::orders::{OrderMeta, OrderResponse}; +use bitfinex::orders::{CancelOrderForm, OrderMeta, OrderResponse}; use bitfinex::ticker::TradingPairTicker; use chrono::{DateTime, NaiveDate, NaiveDateTime, Utc}; use log::debug; @@ -13,12 +13,17 @@ use log::debug; use crate::currency::SymbolPair; use crate::models::{ ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionState, - PriceTicker, + PriceTicker, TradingPlatform, }; use crate::BoxError; +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum Exchange { + Bitfinex, +} + #[derive(Eq, PartialEq, Hash, Clone, Debug)] -pub enum ExchangeKind { +pub enum ExchangeDetails { Bitfinex { api_key: String, api_secret: String }, } @@ -26,14 +31,14 @@ pub enum ExchangeKind { /// because it already uses an [`Arc`] internally. #[derive(Clone, Debug)] pub struct Client { - exchange: ExchangeKind, + exchange: ExchangeDetails, inner: Arc>, } impl Client { - pub fn new(exchange: &ExchangeKind) -> Self { + pub fn new(exchange: &ExchangeDetails) -> Self { let inner = match &exchange { - ExchangeKind::Bitfinex { + ExchangeDetails::Bitfinex { api_key, api_secret, } => BitfinexConnector::new(&api_key, &api_secret), @@ -60,7 +65,7 @@ impl Client { self.inner.active_orders(pair).await } - pub async fn submit_order(&self, order: OrderForm) -> Result { + pub async fn submit_order(&self, order: &OrderForm) -> Result { self.inner.submit_order(order).await } @@ -80,7 +85,7 @@ pub trait Connector: Send + Sync { async fn current_prices(&self, pair: &SymbolPair) -> Result; async fn order_book(&self, pair: &SymbolPair) -> Result; async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError>; - async fn submit_order(&self, order: OrderForm) -> Result; + async fn submit_order(&self, order: &OrderForm) -> Result; async fn cancel_order(&self, order: &ActiveOrder) -> Result; } @@ -111,7 +116,7 @@ impl BitfinexConnector { } } - fn format_trading_pair(&self, pair: &SymbolPair) -> String { + fn format_trading_pair(pair: &SymbolPair) -> String { if pair.to_string().to_lowercase().contains("test") { format!("{}:{}", pair.base(), pair.quote()) } else { @@ -143,11 +148,9 @@ impl Connector for BitfinexConnector { } async fn current_prices(&self, pair: &SymbolPair) -> Result { - let ticker: TradingPairTicker = self - .bfx - .ticker - .trading_pair(self.format_trading_pair(pair)) - .await?; + let symbol_name = BitfinexConnector::format_trading_pair(pair); + + let ticker: TradingPairTicker = self.bfx.ticker.trading_pair(symbol_name).await?; Ok(ticker) } @@ -156,37 +159,52 @@ impl Connector for BitfinexConnector { unimplemented!() } - async fn submit_order(&self, order: OrderForm) -> Result { - // TODO: change trading pair formatting. awful. - let order_form = match &self.affiliate_code { - Some(affiliate_code) => bitfinex::orders::OrderForm::new( - format!("t{}", self.format_trading_pair(order.pair())), - *order.price(), - *order.amount(), - order.kind().into(), - ) - .with_meta(OrderMeta::new(affiliate_code.clone())), - None => bitfinex::orders::OrderForm::new( - format!("t{}", self.format_trading_pair(order.pair())), - *order.price(), - *order.amount(), - order.kind().into(), - ), - }; + async fn submit_order(&self, order: &OrderForm) -> Result { + let symbol_name = format!("t{}", BitfinexConnector::format_trading_pair(order.pair())); + + let order_form = match order.kind() { + OrderKind::Limit { price, amount } => { + bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) + } + OrderKind::Market { amount } => { + bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into()) + } + OrderKind::Stop { price, amount } => { + bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) + } + OrderKind::StopLimit { + price, + amount, + limit_price, + } => bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) + .with_price_aux_limit(limit_price)?, + OrderKind::TrailingStop { distance, amount } => { + bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into()) + .with_price_trailing(distance)? + } + OrderKind::FillOrKill { price, amount } => { + bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) + } + OrderKind::ImmediateOrCancel { price, amount } => { + bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) + } + } + .with_meta(OrderMeta::new( + BitfinexConnector::AFFILIATE_CODE.to_string(), + )); let response = self.bfx.orders.submit_order(&order_form).await?; - Ok(response.try_into()?) + Ok((&response).try_into()?) } async fn order_book(&self, pair: &SymbolPair) -> Result { + let symbol_name = BitfinexConnector::format_trading_pair(pair); + let x = self .bfx .book - .trading_pair( - self.format_trading_pair(&pair), - bitfinex::book::BookPrecision::P0, - ) + .trading_pair(symbol_name, bitfinex::book::BookPrecision::P0) .await?; let entries = x @@ -202,39 +220,35 @@ impl Connector for BitfinexConnector { } async fn cancel_order(&self, order: &ActiveOrder) -> Result { - let date = DateTime::::from_utc( - NaiveDateTime::from_timestamp(order.update_timestamp as i64, 0), - Utc, - ); - let cancel_form = bitfinex::orders::CancelOrderForm::new(order.id, order.client_id, date); + let cancel_form = order.into(); - Ok(self - .bfx - .orders - .cancel_order(&cancel_form) - .await? - .try_into()?) + Ok((&self.bfx.orders.cancel_order(&cancel_form).await?).try_into()?) } } -impl TryFrom for ActiveOrder { +impl From<&ActiveOrder> for CancelOrderForm { + fn from(o: &ActiveOrder) -> Self { + Self::from_id(o.id) + } +} + +impl TryFrom<&bitfinex::orders::OrderResponse> for ActiveOrder { type Error = BoxError; - fn try_from(response: OrderResponse) -> Result { + fn try_from(response: &OrderResponse) -> Result { Ok(Self { + exchange: Exchange::Bitfinex, id: response.id(), group_id: response.gid(), - client_id: response.cid(), + client_id: Some(response.cid()), symbol: SymbolPair::from_str(response.symbol())?, - creation_timestamp: response.mts_create(), - update_timestamp: response.mts_update(), - amount: response.amount(), - amount_original: response.amount_orig(), - order_type: (&response.order_type()).into(), - previous_order_type: response.prev_order_type().map(|x| (&x).into()), - price: response.price(), - price_avg: response.price_avg(), - hidden: response.hidden(), + current_form: OrderForm::new( + SymbolPair::from_str(response.symbol())?, + response.into(), + response.into(), + ), + creation_timestamp: 0, + update_timestamp: 0, }) } } @@ -266,62 +280,90 @@ impl TryInto for bitfinex::positions::Position { } } -impl From<&OrderKind> for bitfinex::orders::OrderKind { - fn from(o: &OrderKind) -> Self { - match o { - OrderKind::Limit => Self::Limit, - OrderKind::ExchangeLimit => Self::ExchangeLimit, - OrderKind::Market => Self::Market, - OrderKind::ExchangeMarket => Self::ExchangeMarket, - OrderKind::Stop => Self::Stop, - OrderKind::ExchangeStop => Self::ExchangeStop, - OrderKind::StopLimit => Self::StopLimit, - OrderKind::ExchangeStopLimit => Self::ExchangeStopLimit, - OrderKind::TrailingStop => Self::TrailingStop, - OrderKind::Fok => Self::Fok, - OrderKind::ExchangeFok => Self::ExchangeFok, - OrderKind::Ioc => Self::Ioc, - OrderKind::ExchangeIoc => Self::ExchangeIoc, +impl From<&OrderForm> for bitfinex::orders::OrderKind { + fn from(o: &OrderForm) -> Self { + match o.platform() { + TradingPlatform::Exchange => match o.kind() { + OrderKind::Limit { .. } => bitfinex::orders::OrderKind::ExchangeLimit, + OrderKind::Market { .. } => bitfinex::orders::OrderKind::ExchangeMarket, + OrderKind::Stop { .. } => bitfinex::orders::OrderKind::ExchangeStop, + OrderKind::StopLimit { .. } => bitfinex::orders::OrderKind::ExchangeStopLimit, + OrderKind::TrailingStop { .. } => bitfinex::orders::OrderKind::ExchangeTrailingStop, + OrderKind::FillOrKill { .. } => bitfinex::orders::OrderKind::ExchangeFok, + OrderKind::ImmediateOrCancel { .. } => bitfinex::orders::OrderKind::ExchangeIoc, + }, + TradingPlatform::Margin => match o.kind() { + OrderKind::Limit { .. } => bitfinex::orders::OrderKind::Limit, + OrderKind::Market { .. } => bitfinex::orders::OrderKind::Market, + OrderKind::Stop { .. } => bitfinex::orders::OrderKind::Stop, + OrderKind::StopLimit { .. } => bitfinex::orders::OrderKind::StopLimit, + OrderKind::TrailingStop { .. } => bitfinex::orders::OrderKind::TrailingStop, + OrderKind::FillOrKill { .. } => bitfinex::orders::OrderKind::Fok, + OrderKind::ImmediateOrCancel { .. } => bitfinex::orders::OrderKind::Ioc, + }, + _ => unimplemented!(), } } } -impl From<&bitfinex::orders::OrderKind> for OrderKind { - fn from(o: &bitfinex::orders::OrderKind) -> Self { - match o { - bitfinex::orders::OrderKind::Limit => OrderKind::Limit, - bitfinex::orders::OrderKind::ExchangeLimit => OrderKind::ExchangeLimit, - bitfinex::orders::OrderKind::Market => OrderKind::Market, - bitfinex::orders::OrderKind::ExchangeMarket => OrderKind::ExchangeMarket, - bitfinex::orders::OrderKind::Stop => OrderKind::Stop, - bitfinex::orders::OrderKind::ExchangeStop => OrderKind::ExchangeStop, - bitfinex::orders::OrderKind::StopLimit => OrderKind::StopLimit, - bitfinex::orders::OrderKind::ExchangeStopLimit => OrderKind::ExchangeStopLimit, - bitfinex::orders::OrderKind::TrailingStop => OrderKind::TrailingStop, - bitfinex::orders::OrderKind::Fok => OrderKind::Fok, - bitfinex::orders::OrderKind::ExchangeFok => OrderKind::ExchangeFok, - bitfinex::orders::OrderKind::Ioc => OrderKind::Ioc, - bitfinex::orders::OrderKind::ExchangeIoc => OrderKind::ExchangeIoc, +impl From<&bitfinex::orders::OrderResponse> for TradingPlatform { + fn from(response: &OrderResponse) -> Self { + match response.order_type() { + bitfinex::orders::OrderKind::Limit + | bitfinex::orders::OrderKind::Market + | bitfinex::orders::OrderKind::StopLimit + | bitfinex::orders::OrderKind::Stop + | bitfinex::orders::OrderKind::TrailingStop + | bitfinex::orders::OrderKind::Fok + | bitfinex::orders::OrderKind::Ioc => Self::Margin, + _ => Self::Exchange, } } } -impl From for bitfinex::orders::OrderKind { - fn from(o: OrderKind) -> Self { - match o { - OrderKind::Limit => Self::Limit, - OrderKind::ExchangeLimit => Self::ExchangeLimit, - OrderKind::Market => Self::Market, - OrderKind::ExchangeMarket => Self::ExchangeMarket, - OrderKind::Stop => Self::Stop, - OrderKind::ExchangeStop => Self::ExchangeStop, - OrderKind::StopLimit => Self::StopLimit, - OrderKind::ExchangeStopLimit => Self::ExchangeStopLimit, - OrderKind::TrailingStop => Self::TrailingStop, - OrderKind::Fok => Self::Fok, - OrderKind::ExchangeFok => Self::ExchangeFok, - OrderKind::Ioc => Self::Ioc, - OrderKind::ExchangeIoc => Self::ExchangeIoc, +impl From<&bitfinex::orders::OrderResponse> for OrderKind { + fn from(response: &OrderResponse) -> Self { + match response.order_type() { + bitfinex::orders::OrderKind::Limit | bitfinex::orders::OrderKind::ExchangeLimit => { + Self::Limit { + price: response.price(), + amount: response.amount(), + } + } + bitfinex::orders::OrderKind::Market | bitfinex::orders::OrderKind::ExchangeMarket => { + Self::Market { + amount: response.amount(), + } + } + bitfinex::orders::OrderKind::Stop | bitfinex::orders::OrderKind::ExchangeStop => { + Self::Stop { + price: response.price(), + amount: response.amount(), + } + } + bitfinex::orders::OrderKind::StopLimit + | bitfinex::orders::OrderKind::ExchangeStopLimit => Self::StopLimit { + price: response.price(), + amount: response.amount(), + limit_price: response.price_aux_limit(), + }, + bitfinex::orders::OrderKind::TrailingStop + | bitfinex::orders::OrderKind::ExchangeTrailingStop => Self::TrailingStop { + distance: response.price_trailing(), + amount: response.amount(), + }, + bitfinex::orders::OrderKind::Fok | bitfinex::orders::OrderKind::ExchangeFok => { + Self::FillOrKill { + price: response.price(), + amount: response.amount(), + } + } + bitfinex::orders::OrderKind::Ioc | bitfinex::orders::OrderKind::ExchangeIoc => { + Self::ImmediateOrCancel { + price: response.price(), + amount: response.amount(), + } + } } } } diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 44e57d6..2a23e0a 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -4,7 +4,7 @@ use std::future::Future; use tokio::task::JoinHandle; use crate::managers::{OptionUpdate, OrderManager, PositionManager, PriceManager}; -use crate::models::{OrderKind, Position, PositionProfitState}; +use crate::models::{OrderForm, OrderKind, Position, PositionProfitState}; use tokio::sync::oneshot; #[derive(Debug)] @@ -20,7 +20,7 @@ pub enum Message { }, ClosePosition { position: Position, - order_kind: OrderKind, + order_form: OrderForm, }, OpenPosition, } diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index cdc9d7e..20e0bc3 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -10,10 +10,12 @@ use tokio::sync::mpsc::channel; use tokio::sync::mpsc::{Receiver, Sender}; use tokio::sync::oneshot; -use crate::connectors::{Client, ExchangeKind}; +use crate::connectors::{Client, ExchangeDetails}; use crate::currency::SymbolPair; use crate::events::{ActorMessage, Event, Message}; -use crate::models::{ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker}; +use crate::models::{ + ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker, TradingPlatform, +}; use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop}; use crate::BoxError; @@ -356,7 +358,7 @@ impl OrderManagerHandle { pub async fn close_position( &mut self, position: Position, - order_kind: OrderKind, + order_form: OrderForm, ) -> Result { let (send, recv) = oneshot::channel(); @@ -364,7 +366,7 @@ impl OrderManagerHandle { .send(ActorMessage { message: Message::ClosePosition { position, - order_kind, + order_form, }, respond_to: send, }) @@ -407,8 +409,8 @@ impl OrderManager { } Message::ClosePosition { position, - order_kind, - } => self.close_position(&position, &order_kind).await?, + order_form, + } => self.close_position(&position, &order_form).await?, _ => {} }; @@ -420,12 +422,11 @@ impl OrderManager { pub async fn close_position( &mut self, position: &Position, - order_kind: &OrderKind, + order_form: &OrderForm, ) -> Result<(), BoxError> { let open_order = self.tracked_positions.get(&position.id()); debug!("Closing position #{}", position.id()); - debug!("Getting current prices..."); let order_book = self.client.order_book(&self.pair).await?; @@ -442,24 +443,11 @@ impl OrderManager { if let Some(messages) = messages { for m in messages { match m { - Message::ClosePosition { - position, - order_kind, - } => { - // TODO FIXME - match order_kind { - OrderKind::Market => { - info!("Closing open order with a market order."); - if let Ok(_) = self - .submit_market_order(1.0, position.amount().neg()) - .await - { - // cancel active order - info!("Cancelling open order #{}", open_order.id); - self.client.cancel_order(open_order).await?; - } - } - _ => {} + Message::ClosePosition { order_form, .. } => { + info!("Closing open order with a {} order", order_form.kind()); + if let Ok(_) = self.client.submit_order(&order_form).await { + info!("Cancelling open order #{}", open_order.id); + self.client.cancel_order(open_order).await?; } } _ => { @@ -472,27 +460,18 @@ impl OrderManager { None => { let closing_price = self.best_closing_price(&position, &order_book); - let active_order = match order_kind { - OrderKind::Limit => { - self.submit_limit_order(closing_price, position.amount().neg()) - .await? - } - OrderKind::ExchangeLimit => { - self.submit_exchange_limit_order(closing_price, position.amount().neg()) - .await? - } - OrderKind::Market => { - self.submit_market_order(closing_price, position.amount().neg()) - .await? - } - OrderKind::ExchangeMarket => { - self.submit_exchange_market_order(closing_price, position.amount().neg()) - .await? - } - _ => { - unimplemented!() - } - }; + // TODO: hardocoded platform to Margin! + let order_form = OrderForm::new( + self.pair.clone(), + OrderKind::Limit { + price: closing_price, + amount: position.amount().neg(), + }, + TradingPlatform::Margin, + ); + + info!("Submitting {} order", order_form.kind()); + let active_order = self.client.submit_order(&order_form).await?; self.tracked_positions.insert(position.id(), active_order); } @@ -501,68 +480,6 @@ impl OrderManager { Ok(()) } - pub async fn submit_limit_order( - &self, - closing_price: f64, - amount: f64, - ) -> Result { - info!( - "Submitting exchange limit order of {} {}...", - amount, - self.pair.base() - ); - let order_form = OrderForm::new(&self.pair, closing_price, amount, OrderKind::Limit); - - Ok(self.client.submit_order(order_form).await?) - } - - pub async fn submit_exchange_limit_order( - &self, - closing_price: f64, - amount: f64, - ) -> Result { - info!( - "Submitting exchange limit order of {} {}...", - amount, - self.pair.base() - ); - let order_form = - OrderForm::new(&self.pair, closing_price, amount, OrderKind::ExchangeLimit); - - Ok(self.client.submit_order(order_form).await?) - } - - pub async fn submit_market_order( - &self, - closing_price: f64, - amount: f64, - ) -> Result { - info!( - "Submitting market order of {} {}...", - amount, - self.pair.base() - ); - let order_form = OrderForm::new(&self.pair, closing_price, amount, OrderKind::Market); - - Ok(self.client.submit_order(order_form).await?) - } - - pub async fn submit_exchange_market_order( - &self, - closing_price: f64, - amount: f64, - ) -> Result { - info!( - "Submitting market order of {} {}...", - amount, - self.pair.base() - ); - let order_form = - OrderForm::new(&self.pair, closing_price, amount, OrderKind::ExchangeMarket); - - Ok(self.client.submit_order(order_form).await?) - } - pub fn update(&self) -> Result { // TODO: implement me Ok((None, None)) @@ -635,10 +552,10 @@ impl PairManager { match m { Message::ClosePosition { position, - order_kind, + order_form, } => { self.order_manager - .close_position(position, order_kind) + .close_position(position, order_form) .await?; } _ => {} @@ -651,13 +568,13 @@ impl PairManager { } pub struct ExchangeManager { - kind: ExchangeKind, + kind: ExchangeDetails, pair_managers: Vec, client: Client, } impl ExchangeManager { - pub fn new(kind: &ExchangeKind, pairs: &Vec) -> Self { + pub fn new(kind: &ExchangeDetails, pairs: &Vec) -> Self { let client = Client::new(kind); let pair_managers = pairs .into_iter() diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index 5c89a44..1c5713d 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -1,9 +1,11 @@ -use std::fmt::Display; +use std::fmt; +use std::fmt::{Display, Formatter}; use std::hash::{Hash, Hasher}; use chrono::{DateTime, TimeZone}; use float_cmp::ApproxEq; +use crate::connectors::{Exchange, ExchangeDetails}; use crate::currency::SymbolPair; use crate::BoxError; @@ -108,24 +110,19 @@ impl OrderBook { #[derive(Clone, Debug)] pub struct ActiveOrder { + pub(crate) exchange: Exchange, pub(crate) id: u64, pub(crate) group_id: Option, - pub(crate) client_id: u64, + pub(crate) client_id: Option, pub(crate) symbol: SymbolPair, + pub(crate) current_form: OrderForm, pub(crate) creation_timestamp: u64, pub(crate) update_timestamp: u64, - pub(crate) amount: f64, - pub(crate) amount_original: f64, - pub(crate) order_type: OrderKind, - pub(crate) previous_order_type: Option, - pub(crate) price: f64, - pub(crate) price_avg: f64, - pub(crate) hidden: bool, } impl Hash for ActiveOrder { fn hash(&self, state: &mut H) { - state.write(&self.id.to_le_bytes()) + state.write(&self.id.to_le_bytes()); } } @@ -137,118 +134,195 @@ impl PartialEq for ActiveOrder { impl Eq for ActiveOrder {} -#[derive(Copy, Clone, Debug, Hash)] -pub enum OrderKind { - Limit, - ExchangeLimit, - Market, - ExchangeMarket, - Stop, - ExchangeStop, - StopLimit, - ExchangeStopLimit, - TrailingStop, - Fok, - ExchangeFok, - Ioc, - ExchangeIoc, +#[derive(Debug, Clone, Copy)] +pub enum TradingPlatform { + Exchange, + Derivative, + Funding, + Margin, } -#[derive(Clone, Debug)] +impl TradingPlatform { + pub fn as_str(&self) -> &'static str { + match self { + TradingPlatform::Exchange => "Exchange", + TradingPlatform::Derivative => "Derivative", + TradingPlatform::Funding => "Funding", + TradingPlatform::Margin => "Margin", + } + } +} + +impl Display for TradingPlatform { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +#[derive(Copy, Clone, Debug)] +pub enum OrderKind { + Limit { + price: f64, + amount: f64, + }, + Market { + amount: f64, + }, + Stop { + price: f64, + amount: f64, + }, + StopLimit { + price: f64, + amount: f64, + limit_price: f64, + }, + TrailingStop { + distance: f64, + amount: f64, + }, + FillOrKill { + price: f64, + amount: f64, + }, + ImmediateOrCancel { + price: f64, + amount: f64, + }, +} +impl OrderKind { + pub fn as_str(&self) -> &'static str { + match self { + OrderKind::Limit { .. } => "Limit", + OrderKind::Market { .. } => "Market", + OrderKind::Stop { .. } => "Stop", + OrderKind::StopLimit { .. } => "Stop Limit", + OrderKind::TrailingStop { .. } => "Trailing Stop", + OrderKind::FillOrKill { .. } => "Fill or Kill", + OrderKind::ImmediateOrCancel { .. } => "Immediate or Cancel", + } + } +} + +impl Display for OrderKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + OrderKind::Limit { price, amount } => { + write!( + f, + "[{} | Price: {:0.5}, Amount: {:0.5}]", + self.as_str(), + price, + amount + ) + } + OrderKind::Market { amount } => { + write!(f, "[{} | Amount: {:0.5}]", self.as_str(), amount) + } + OrderKind::Stop { price, amount } => { + write!( + f, + "[{} | Price: {:0.5}, Amount: {:0.5}]", + self.as_str(), + price, + amount + ) + } + OrderKind::StopLimit { + price, + amount, + limit_price, + } => { + write!( + f, + "[{} | Price: {:0.5}, Amount: {:0.5}, Limit Price: {:0.5}]", + self.as_str(), + price, + amount, + limit_price + ) + } + OrderKind::TrailingStop { distance, amount } => { + write!( + f, + "[{} | Distance: {:0.5}, Amount: {:0.5}]", + self.as_str(), + distance, + amount + ) + } + OrderKind::FillOrKill { price, amount } => { + write!( + f, + "[{} | Price: {:0.5}, Amount: {:0.5}]", + self.as_str(), + price, + amount + ) + } + OrderKind::ImmediateOrCancel { price, amount } => { + write!( + f, + "[{} | Price: {:0.5}, Amount: {:0.5}]", + self.as_str(), + price, + amount + ) + } + } + } +} + +#[derive(Debug, Clone)] pub struct OrderForm { - /// Order Type: LIMIT, EXCHANGE LIMIT, MARKET, EXCHANGE MARKET, - /// STOP, EXCHANGE STOP, STOP LIMIT, EXCHANGE STOP LIMIT, - /// TRAILING STOP, EXCHANGE TRAILING STOP, FOK, - /// EXCHANGE FOK, IOC, EXCHANGE IOC - kind: OrderKind, - /// Symbol for desired pair pair: SymbolPair, - /// Price of order - price: f64, - /// Amount of order (positive for buy, negative for sell) - amount: f64, - /// Set the leverage for a derivative order, supported by derivative symbol orders only. - /// The value should be between 1 and 100 inclusive. - /// The field is optional, if omitted the default leverage value of 10 will be used. - leverage: Option, - /// The trailing price for a trailing stop order - price_trailing: Option, - /// Auxiliary Limit price (for STOP LIMIT) - price_aux_limit: Option, - /// Time-In-Force: datetime for automatic order cancellation (ie. 2020-01-01 10:45:23) ) - tif: Option, + kind: OrderKind, + platform: TradingPlatform, } impl OrderForm { - pub fn new(pair: &SymbolPair, price: f64, amount: f64, kind: OrderKind) -> Self { - OrderForm { - kind, - pair: pair.clone(), - price, - amount, - leverage: None, - price_trailing: None, - price_aux_limit: None, - tif: None, + pub fn new(pair: SymbolPair, order_kind: OrderKind, platform: TradingPlatform) -> Self { + Self { + pair, + kind: order_kind, + platform, } } - pub fn with_leverage(mut self, leverage: u32) -> Self { - self.leverage = Some(leverage); - self - } - - pub fn with_price_trailing(mut self, trailing: f64) -> Result { - match self.kind { - OrderKind::TrailingStop => { - self.price_trailing = Some(trailing.to_string()); - Ok(self) - } - _ => Err("Invalid order type.".into()), - } - } - - pub fn with_price_aux_limit(mut self, limit: f64) -> Result { - match self.kind { - OrderKind::StopLimit | OrderKind::ExchangeStopLimit => { - self.price_aux_limit = Some(limit.to_string()); - Ok(self) - } - _ => Err("Invalid order type.".into()), - } - } - - pub fn with_tif(mut self, tif: DateTime) -> Self - where - T::Offset: Display, - { - self.tif = Some(tif.format("%Y-%m-%d %H:%M:%S").to_string()); - self + pub fn pair(&self) -> &SymbolPair { + &self.pair } pub fn kind(&self) -> OrderKind { self.kind } - pub fn pair(&self) -> &SymbolPair { - &self.pair + + pub fn platform(&self) -> &TradingPlatform { + &self.platform } - pub fn price(&self) -> &f64 { - &self.price + + pub fn amount(&self) -> f64 { + match self.kind { + OrderKind::Limit { amount, .. } => amount, + OrderKind::Market { amount } => amount, + OrderKind::Stop { amount, .. } => amount, + OrderKind::StopLimit { amount, .. } => amount, + OrderKind::TrailingStop { amount, .. } => amount, + OrderKind::FillOrKill { amount, .. } => amount, + OrderKind::ImmediateOrCancel { amount, .. } => amount, + } } - pub fn amount(&self) -> &f64 { - &self.amount - } - pub fn leverage(&self) -> Option { - self.leverage - } - pub fn price_trailing(&self) -> &Option { - &self.price_trailing - } - pub fn price_aux_limit(&self) -> &Option { - &self.price_aux_limit - } - pub fn tif(&self) -> &Option { - &self.tif + + pub fn price(&self) -> Option { + match self.kind { + OrderKind::Limit { price, .. } => Some(price), + OrderKind::Market { .. } => None, + OrderKind::Stop { price, .. } => Some(price), + OrderKind::StopLimit { price, .. } => Some(price), + OrderKind::TrailingStop { .. } => None, + OrderKind::FillOrKill { price, .. } => Some(price), + OrderKind::ImmediateOrCancel { price, .. } => Some(price), + } } } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index b954bd8..9c5c759 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use std::fmt::{Debug, Formatter}; +use std::ops::Neg; use dyn_clone::DynClone; use log::{debug, info}; @@ -9,6 +10,7 @@ use crate::events::{Event, EventKind, EventMetadata, Message}; use crate::managers::{OrderManager, PositionManager, TrackedPositionsMap}; use crate::models::{ ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionProfitState, + TradingPlatform, }; use crate::BoxError; @@ -111,7 +113,14 @@ impl PositionStrategy for TrailingStop { debug!("Inserting close position message..."); messages.push(Message::ClosePosition { position: position.clone(), - order_kind: OrderKind::Limit, + order_form: OrderForm::new( + position.pair().clone(), + OrderKind::Limit { + price: 0.0, + amount: position.amount(), + }, + TradingPlatform::Margin, + ), }); PositionProfitState::Critical } @@ -195,7 +204,7 @@ pub struct FastOrderStrategy { impl Default for FastOrderStrategy { fn default() -> Self { - Self { threshold: 0.2 } + Self { threshold: 0.05 } } } @@ -224,7 +233,7 @@ impl OrderStrategy for FastOrderStrategy { // long let offer_comparison = { - if order.amount > 0.0 { + if order.current_form.amount() > 0.0 { order_book.highest_bid() } else { order_book.lowest_ask() @@ -233,12 +242,22 @@ impl OrderStrategy for FastOrderStrategy { // if the best offer is higher than our threshold, // ask the manager to close the position with a market order - let delta = (1.0 - (offer_comparison / order.price)) * 100.0; + let order_price = order + .current_form + .price() + .ok_or("The active order does not have a price!")?; + let delta = (1.0 - (offer_comparison / order_price)) * 100.0; if delta > self.threshold { messages.push(Message::ClosePosition { position: active_position.clone(), - order_kind: OrderKind::Market, + order_form: OrderForm::new( + order.symbol.clone(), + OrderKind::Market { + amount: order.current_form.amount(), + }, + order.current_form.platform().clone(), + ), }) } -- 2.47.2 From 9b92d38318c80961ca4079ec035b99978d60ca9a Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sun, 24 Jan 2021 13:41:18 +0000 Subject: [PATCH 071/127] retrieving updated information on open orders before calling strategy --- rustybot/src/connectors.rs | 89 ++++++++++++++++++++++++++++++++++++-- rustybot/src/managers.rs | 81 +++++++++++++++++++++------------- 2 files changed, 135 insertions(+), 35 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 06b4756..c71cc6a 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -155,8 +155,10 @@ impl Connector for BitfinexConnector { Ok(ticker) } - async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError> { - unimplemented!() + async fn active_orders(&self, _: &SymbolPair) -> Result, BoxError> { + let response = self.bfx.orders.active_orders().await?; + + Ok(response.iter().map(Into::into).collect()) } async fn submit_order(&self, order: &OrderForm) -> Result { @@ -321,6 +323,21 @@ impl From<&bitfinex::orders::OrderResponse> for TradingPlatform { } } +impl From<&bitfinex::orders::ActiveOrder> for TradingPlatform { + fn from(response: &bitfinex::orders::ActiveOrder) -> Self { + match response.order_type() { + bitfinex::orders::OrderKind::Limit + | bitfinex::orders::OrderKind::Market + | bitfinex::orders::OrderKind::StopLimit + | bitfinex::orders::OrderKind::Stop + | bitfinex::orders::OrderKind::TrailingStop + | bitfinex::orders::OrderKind::Fok + | bitfinex::orders::OrderKind::Ioc => Self::Margin, + _ => Self::Exchange, + } + } +} + impl From<&bitfinex::orders::OrderResponse> for OrderKind { fn from(response: &OrderResponse) -> Self { match response.order_type() { @@ -345,11 +362,11 @@ impl From<&bitfinex::orders::OrderResponse> for OrderKind { | bitfinex::orders::OrderKind::ExchangeStopLimit => Self::StopLimit { price: response.price(), amount: response.amount(), - limit_price: response.price_aux_limit(), + limit_price: response.price_aux_limit().expect("Limit price not found!"), }, bitfinex::orders::OrderKind::TrailingStop | bitfinex::orders::OrderKind::ExchangeTrailingStop => Self::TrailingStop { - distance: response.price_trailing(), + distance: response.price_trailing().expect("Distance not found!"), amount: response.amount(), }, bitfinex::orders::OrderKind::Fok | bitfinex::orders::OrderKind::ExchangeFok => { @@ -368,6 +385,70 @@ impl From<&bitfinex::orders::OrderResponse> for OrderKind { } } +impl From<&bitfinex::orders::ActiveOrder> for OrderKind { + fn from(response: &bitfinex::orders::ActiveOrder) -> Self { + match response.order_type() { + bitfinex::orders::OrderKind::Limit | bitfinex::orders::OrderKind::ExchangeLimit => { + Self::Limit { + price: response.price(), + amount: response.amount(), + } + } + bitfinex::orders::OrderKind::Market | bitfinex::orders::OrderKind::ExchangeMarket => { + Self::Market { + amount: response.amount(), + } + } + bitfinex::orders::OrderKind::Stop | bitfinex::orders::OrderKind::ExchangeStop => { + Self::Stop { + price: response.price(), + amount: response.amount(), + } + } + bitfinex::orders::OrderKind::StopLimit + | bitfinex::orders::OrderKind::ExchangeStopLimit => Self::StopLimit { + price: response.price(), + amount: response.amount(), + limit_price: response.price_aux_limit().expect("Limit price not found!"), + }, + bitfinex::orders::OrderKind::TrailingStop + | bitfinex::orders::OrderKind::ExchangeTrailingStop => Self::TrailingStop { + distance: response.price_trailing().expect("Distance not found!"), + amount: response.amount(), + }, + bitfinex::orders::OrderKind::Fok | bitfinex::orders::OrderKind::ExchangeFok => { + Self::FillOrKill { + price: response.price(), + amount: response.amount(), + } + } + bitfinex::orders::OrderKind::Ioc | bitfinex::orders::OrderKind::ExchangeIoc => { + Self::ImmediateOrCancel { + price: response.price(), + amount: response.amount(), + } + } + } + } +} + +impl From<&bitfinex::orders::ActiveOrder> for ActiveOrder { + fn from(order: &bitfinex::orders::ActiveOrder) -> Self { + let pair = SymbolPair::from_str(&order.symbol()).expect("Invalid symbol!"); + + Self { + exchange: Exchange::Bitfinex, + id: order.id(), + group_id: order.group_id().map(|x| x as u64), + client_id: Some(order.client_id()), + symbol: pair.clone(), + current_form: OrderForm::new(pair, order.into(), order.into()), + creation_timestamp: order.creation_timestamp(), + update_timestamp: order.update_timestamp(), + } + } +} + impl From for PriceTicker { fn from(t: TradingPairTicker) -> Self { Self { diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 20e0bc3..912e890 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -319,7 +319,8 @@ impl PositionManager { * ORDERS ******************/ -pub type TrackedPositionsMap = HashMap; +// Position ID: Order ID +pub type TrackedPositionsMap = HashMap; pub struct OrderManagerHandle { sender: Sender, @@ -405,7 +406,7 @@ impl OrderManager { pub async fn handle_message(&mut self, msg: ActorMessage) -> Result<(), BoxError> { match msg.message { Message::Update { .. } => { - self.update(); + self.update().await?; } Message::ClosePosition { position, @@ -424,39 +425,23 @@ impl OrderManager { position: &Position, order_form: &OrderForm, ) -> Result<(), BoxError> { - let open_order = self.tracked_positions.get(&position.id()); + info!("Closing position #{}", position.id()); + + debug!("Retrieving open orders..."); + let open_orders = self.client.active_orders(&self.pair).await?; + + let opt_position_order = open_orders + .iter() + .find(|x| x.current_form.amount().neg() == position.amount()); - debug!("Closing position #{}", position.id()); debug!("Getting current prices..."); let order_book = self.client.order_book(&self.pair).await?; // checking if the position has an open order. // If so, the strategy method is called, otherwise we open // an undercut limit order at the best current price. - match open_order { - Some(open_order) => { - debug!("There is an open order. Calling strategy."); - let (_, messages) = - self.strategy - .on_position_close(open_order, position, &order_book)?; - - if let Some(messages) = messages { - for m in messages { - match m { - Message::ClosePosition { order_form, .. } => { - info!("Closing open order with a {} order", order_form.kind()); - if let Ok(_) = self.client.submit_order(&order_form).await { - info!("Cancelling open order #{}", open_order.id); - self.client.cancel_order(open_order).await?; - } - } - _ => { - debug!("Received unsupported message from order strategy. Unimplemented.") - } - } - } - } - } + match opt_position_order { + // No open order, undercutting best price with limit order None => { let closing_price = self.best_closing_price(&position, &order_book); @@ -471,16 +456,50 @@ impl OrderManager { ); info!("Submitting {} order", order_form.kind()); - let active_order = self.client.submit_order(&order_form).await?; + if let Err(e) = self.client.submit_order(&order_form).await { + error!( + "Could not submit {} to close position #{}: {}", + order_form.kind(), + position.id(), + e + ); + return Err(e); + } + } + Some(active_order) => { + debug!( + "Found open order, calling \"{}\" strategy.", + self.strategy.name() + ); - self.tracked_positions.insert(position.id(), active_order); + let (_, strat_messages) = + self.strategy + .on_position_close(active_order, position, &order_book)?; + + if let Some(messages) = strat_messages { + for m in messages { + match m { + Message::ClosePosition { order_form, .. } => { + info!("Closing open order with a {} order", order_form.kind()); + + if let Ok(_) = self.client.submit_order(&order_form).await { + info!("Cancelling open order #{}", active_order.id); + self.client.cancel_order(active_order).await?; + } + } + _ => { + debug!("Received unsupported message from order strategy. Unimplemented.") + } + } + } + } } } Ok(()) } - pub fn update(&self) -> Result { + pub async fn update(&self) -> Result { // TODO: implement me Ok((None, None)) } -- 2.47.2 From 193feac230f0148c16a7f3367e72ad7a504cfefa Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sun, 24 Jan 2021 14:01:42 +0000 Subject: [PATCH 072/127] joining tasks --- rustybot/src/managers.rs | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 912e890..51c9716 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -427,16 +427,32 @@ impl OrderManager { ) -> Result<(), BoxError> { info!("Closing position #{}", position.id()); - debug!("Retrieving open orders..."); - let open_orders = self.client.active_orders(&self.pair).await?; + debug!("Retrieving open orders and current prices..."); + let (open_orders, order_book) = tokio::join!( + self.client.active_orders(&self.pair), + self.client.order_book(&self.pair) + ); + + let open_orders = match open_orders { + Ok(open_orders) => open_orders, + Err(e) => { + error!("Could not retrieve open orders: {}", e); + return Err(e); + } + }; + + let order_book = match order_book { + Ok(order_book) => order_book, + Err(e) => { + error!("Could not retrieve order book: {}", e); + return Err(e); + } + }; let opt_position_order = open_orders .iter() .find(|x| x.current_form.amount().neg() == position.amount()); - debug!("Getting current prices..."); - let order_book = self.client.order_book(&self.pair).await?; - // checking if the position has an open order. // If so, the strategy method is called, otherwise we open // an undercut limit order at the best current price. @@ -560,9 +576,15 @@ impl PairManager { } pub async fn update_managers(&mut self, tick: u64) -> Result<(), BoxError> { - let (opt_price_events, opt_price_messages) = self.price_manager.update(tick).await?; - let (opt_pos_events, opt_pos_messages) = self.position_manager.update(tick).await?; - let (opt_order_events, opt_order_messages) = self.order_manager.update(tick).await?; + let (price_results, pos_results, order_results) = tokio::join!( + self.price_manager.update(tick), + self.position_manager.update(tick), + self.order_manager.update(tick) + ); + + let (opt_price_events, opt_price_messages) = price_results?; + let (opt_pos_events, opt_pos_messages) = pos_results?; + let (opt_order_events, opt_order_messages) = order_results?; // TODO: to move into Handler? -- 2.47.2 From 394174a244e3e47bd5579d4bfdf6528339f4f955 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sun, 24 Jan 2021 14:06:02 +0000 Subject: [PATCH 073/127] removed old code --- rustybot/src/managers.rs | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 51c9716..f006646 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -641,44 +641,4 @@ impl ExchangeManager { Ok(()) } - - // async fn update_position_managers(&mut self, tick: u64) -> Result { - // let mut res_events = Vec::new(); - // let mut res_messages = Vec::new(); - // - // let mut futures: FuturesUnordered<_> = self - // .pair_managers - // .iter_mut() - // .map(|x| x.position_manager.update(tick)) - // .collect(); - // - // while let Some(future_result) = futures.next().await { - // let (opt_events, opt_messages) = future_result?; - // - // if let Some(events) = opt_events { - // res_events.extend(events); - // } - // - // if let Some(messages) = opt_messages { - // res_messages.extend(messages); - // } - // } - // - // Ok(( - // (!res_events.is_empty()).then_some(res_events), - // (!res_messages.is_empty()).then_some(res_messages), - // )) - // } - // - // async fn update_price_managers(&mut self, tick: u64) -> Result { - // let mut futures: FuturesUnordered<_> = self - // .pair_managers - // .iter_mut() - // .map(|x| x.price_manager.update(tick)) - // .collect(); - // - // while let Some(x) = futures.next().await {} - // - // Ok((None, None)) - // } } -- 2.47.2 From 61cd795cc2d6f7d39919411d7a4fb48be4613e2c Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sun, 24 Jan 2021 14:22:52 +0000 Subject: [PATCH 074/127] split closeposition message in closeposition and submitorder. now close positions retrieves open positions before closing --- rustybot/src/events.rs | 11 ++----- rustybot/src/managers.rs | 64 ++++++++++++++++++++-------------------- rustybot/src/strategy.rs | 15 ++-------- 3 files changed, 38 insertions(+), 52 deletions(-) diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 2a23e0a..57c5968 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -15,14 +15,9 @@ pub struct ActorMessage { #[derive(Debug)] pub enum Message { - Update { - tick: u64, - }, - ClosePosition { - position: Position, - order_form: OrderForm, - }, - OpenPosition, + Update { tick: u64 }, + ClosePosition { position_id: u64 }, + SubmitOrder { order: OrderForm }, } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index f006646..c3998e7 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -356,19 +356,12 @@ impl OrderManagerHandle { Ok(recv.await?) } - pub async fn close_position( - &mut self, - position: Position, - order_form: OrderForm, - ) -> Result { + pub async fn close_position(&mut self, position_id: u64) -> Result { let (send, recv) = oneshot::channel(); self.sender .send(ActorMessage { - message: Message::ClosePosition { - position, - order_form, - }, + message: Message::ClosePosition { position_id }, respond_to: send, }) .await?; @@ -409,9 +402,8 @@ impl OrderManager { self.update().await?; } Message::ClosePosition { - position, - order_form, - } => self.close_position(&position, &order_form).await?, + position_id: position_id, + } => self.close_position(position_id).await?, _ => {} }; @@ -420,17 +412,14 @@ impl OrderManager { Ok(()) } - pub async fn close_position( - &mut self, - position: &Position, - order_form: &OrderForm, - ) -> Result<(), BoxError> { - info!("Closing position #{}", position.id()); + pub async fn close_position(&mut self, position_id: u64) -> Result<(), BoxError> { + info!("Closing position #{}", position_id); - debug!("Retrieving open orders and current prices..."); - let (open_orders, order_book) = tokio::join!( + debug!("Retrieving open orders, positions and current prices..."); + let (open_orders, order_book, open_positions) = tokio::join!( self.client.active_orders(&self.pair), - self.client.order_book(&self.pair) + self.client.order_book(&self.pair), + self.client.active_positions(&self.pair) ); let open_orders = match open_orders { @@ -449,6 +438,22 @@ impl OrderManager { } }; + let position = match open_positions { + Ok(opt_positions) => { + let positions = opt_positions.ok_or::("No open positions".into())?; + + positions + .into_iter() + .find(|x| x.id() == position_id) + .ok_or::("Position #{} not found in open positions".into())? + } + + Err(e) => { + error!("Could not retrieve open positions: {}", e); + return Err(e); + } + }; + let opt_position_order = open_orders .iter() .find(|x| x.current_form.amount().neg() == position.amount()); @@ -490,15 +495,15 @@ impl OrderManager { let (_, strat_messages) = self.strategy - .on_position_close(active_order, position, &order_book)?; + .on_position_close(active_order, &position, &order_book)?; if let Some(messages) = strat_messages { for m in messages { match m { - Message::ClosePosition { order_form, .. } => { - info!("Closing open order with a {} order", order_form.kind()); + Message::SubmitOrder { order } => { + info!("Closing open order with a {} order", order.kind()); - if let Ok(_) = self.client.submit_order(&order_form).await { + if let Ok(_) = self.client.submit_order(&order).await { info!("Cancelling open order #{}", active_order.id); self.client.cancel_order(active_order).await?; } @@ -591,13 +596,8 @@ impl PairManager { if let Some(messages) = opt_pos_messages { for m in messages { match m { - Message::ClosePosition { - position, - order_form, - } => { - self.order_manager - .close_position(position, order_form) - .await?; + Message::ClosePosition { position_id } => { + self.order_manager.close_position(position_id).await?; } _ => {} } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 9c5c759..538fcb1 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -112,15 +112,7 @@ impl PositionStrategy for TrailingStop { } else { debug!("Inserting close position message..."); messages.push(Message::ClosePosition { - position: position.clone(), - order_form: OrderForm::new( - position.pair().clone(), - OrderKind::Limit { - price: 0.0, - amount: position.amount(), - }, - TradingPlatform::Margin, - ), + position_id: position.id(), }); PositionProfitState::Critical } @@ -249,9 +241,8 @@ impl OrderStrategy for FastOrderStrategy { let delta = (1.0 - (offer_comparison / order_price)) * 100.0; if delta > self.threshold { - messages.push(Message::ClosePosition { - position: active_position.clone(), - order_form: OrderForm::new( + messages.push(Message::SubmitOrder { + order: OrderForm::new( order.symbol.clone(), OrderKind::Market { amount: order.current_form.amount(), -- 2.47.2 From 5646b1d5f4023066506de1973643901ea74d8328 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sun, 24 Jan 2021 14:29:12 +0000 Subject: [PATCH 075/127] abs in delta calculation --- rustybot/src/strategy.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 538fcb1..c782dad 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -238,7 +238,7 @@ impl OrderStrategy for FastOrderStrategy { .current_form .price() .ok_or("The active order does not have a price!")?; - let delta = (1.0 - (offer_comparison / order_price)) * 100.0; + let delta = (1.0 - (offer_comparison / order_price).abs()) * 100.0; if delta > self.threshold { messages.push(Message::SubmitOrder { -- 2.47.2 From 50533b053767d7a1388116a88762fbb63b3dceb8 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sun, 24 Jan 2021 14:34:24 +0000 Subject: [PATCH 076/127] abs in delta calculation (in the right place now) --- rustybot/src/strategy.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index c782dad..189c5bf 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -238,7 +238,7 @@ impl OrderStrategy for FastOrderStrategy { .current_form .price() .ok_or("The active order does not have a price!")?; - let delta = (1.0 - (offer_comparison / order_price).abs()) * 100.0; + let delta = (1.0 - (offer_comparison / order_price)).abs() * 100.0; if delta > self.threshold { messages.push(Message::SubmitOrder { -- 2.47.2 From 3a0a420c5a8345cc56e92a4910e34538e3c8665f Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sun, 24 Jan 2021 14:34:51 +0000 Subject: [PATCH 077/127] default threshold to 0.2 --- rustybot/src/strategy.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 189c5bf..b89fbce 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -196,7 +196,7 @@ pub struct FastOrderStrategy { impl Default for FastOrderStrategy { fn default() -> Self { - Self { threshold: 0.05 } + Self { threshold: 0.2 } } } -- 2.47.2 From 1db62c404ea1058bc1261eb6904d85c82ba9deec Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sun, 24 Jan 2021 15:42:54 +0000 Subject: [PATCH 078/127] client active_positions's retrieves positions with accurate profit loss --- rustybot/src/connectors.rs | 20 +++++++++++++++++++- rustybot/src/managers.rs | 1 + rustybot/src/models.rs | 22 ++++++++++++++++++++++ rustybot/src/strategy.rs | 2 +- 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index c71cc6a..8fbdff9 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -54,7 +54,25 @@ impl Client { &self, pair: &SymbolPair, ) -> Result>, BoxError> { - self.inner.active_positions(pair).await + // retrieving open positions and order book to calculate effective profit/loss + let (positions, order_book) = tokio::join!( + self.inner.active_positions(pair), + self.inner.order_book(pair) + ); + + let (mut positions, order_book) = (positions?, order_book?); + let (best_ask, best_bid) = (order_book.lowest_ask(), order_book.highest_bid()); + + // updating positions with effective profit/loss + positions.iter_mut().flatten().for_each(|mut x| { + if x.is_short() { + x.update_profit_loss(best_ask, 0.2); + } else { + x.update_profit_loss(best_bid, 0.2); + } + }); + + Ok(positions) } pub async fn current_prices(&self, pair: &SymbolPair) -> Result { diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index c3998e7..40095fb 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -268,6 +268,7 @@ impl PositionManager { None => return Ok((None, None)), Some(positions) => { + println!("Open positions: {:?}", positions); // checking if there are positions open for our pair match positions .into_iter() diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index 1c5713d..70e0cfe 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -386,6 +386,28 @@ impl Position { self } + pub fn update_profit_loss(&mut self, best_offer: f64, fee_perc: f64) { + let best_offer = best_offer.abs(); + let base_price = self.base_price * (1.0 + fee_perc / 100.0); + + let delta = if self.is_short() { + base_price - best_offer + } else { + best_offer - base_price + }; + + let profit_loss_percentage = + ((1.0 - (base_price + delta) / base_price) * 100.0).abs() * delta.signum(); + + self.pl = delta * self.amount; + self.pl_perc = profit_loss_percentage; + } + + pub fn with_profit_loss(mut self, profit_loss: f64) -> Self { + self.pl = profit_loss; + self + } + pub fn pair(&self) -> &SymbolPair { &self.pair } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index b89fbce..e7c0890 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -96,7 +96,7 @@ impl PositionStrategy for TrailingStop { let mut messages = vec![]; let events = vec![]; - let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE); + let pl_perc = position.pl_perc(); let state = { if pl_perc > TrailingStop::GOOD_PROFIT_PERC { -- 2.47.2 From 4999cdc498f59efa3a587ce5a46e86aec79b9595 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sun, 24 Jan 2021 15:43:31 +0000 Subject: [PATCH 079/127] removed debug message --- rustybot/src/managers.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 40095fb..c3998e7 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -268,7 +268,6 @@ impl PositionManager { None => return Ok((None, None)), Some(positions) => { - println!("Open positions: {:?}", positions); // checking if there are positions open for our pair match positions .into_iter() -- 2.47.2 From 12c9918d2c4c0cc2b61cb16829b76dd3dc9812c0 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sun, 24 Jan 2021 19:36:25 +0000 Subject: [PATCH 080/127] broadcasting messages and events. trailing stop alpha version --- rustybot/Cargo.lock | 47 +++++++++++++ rustybot/Cargo.toml | 3 +- rustybot/src/managers.rs | 55 +++++++++++----- rustybot/src/strategy.rs | 138 ++++++++++++++++++++++++++------------- 4 files changed, 183 insertions(+), 60 deletions(-) diff --git a/rustybot/Cargo.lock b/rustybot/Cargo.lock index d1015ad..3c7e15c 100644 --- a/rustybot/Cargo.lock +++ b/rustybot/Cargo.lock @@ -680,6 +680,28 @@ version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +[[package]] +name = "merge" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10bbef93abb1da61525bbc45eeaff6473a41907d19f8f9aa5168d214e10693e9" +dependencies = [ + "merge_derive", + "num-traits", +] + +[[package]] +name = "merge_derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209d075476da2e63b4b29e72a2ef627b840589588e71400a25e3565c4f849d07" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "mime" version = "0.3.16" @@ -983,6 +1005,30 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.24" @@ -1144,6 +1190,7 @@ dependencies = [ "float-cmp", "futures-util", "log 0.4.11", + "merge", "regex", "tokio 0.2.24", "tokio-tungstenite", diff --git a/rustybot/Cargo.toml b/rustybot/Cargo.toml index fe7f9b2..b3bc398 100644 --- a/rustybot/Cargo.toml +++ b/rustybot/Cargo.toml @@ -18,4 +18,5 @@ log = "0.4" fern = {version = "0.6", features = ["colored"]} chrono = "0.4" byteorder = "1" -float-cmp = "0.8" \ No newline at end of file +float-cmp = "0.8" +merge = "0.1" \ No newline at end of file diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index c3998e7..79b6133 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -1,14 +1,13 @@ use std::collections::HashMap; -use std::ops::Neg; +use std::ops::{Deref, Neg}; use bitfinex::ticker::TradingPairTicker; use futures_util::stream::FuturesUnordered; -use futures_util::StreamExt; use log::{debug, error, info}; use tokio::signal::unix::Signal; use tokio::sync::mpsc::channel; use tokio::sync::mpsc::{Receiver, Sender}; -use tokio::sync::oneshot; +use tokio::sync::{oneshot, RwLock}; use crate::connectors::{Client, ExchangeDetails}; use crate::currency::SymbolPair; @@ -18,6 +17,8 @@ use crate::models::{ }; use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop}; use crate::BoxError; +use merge::Merge; +use tokio::stream::StreamExt; pub type OptionUpdate = (Option>, Option>); @@ -282,14 +283,28 @@ impl PositionManager { // applying strategy to open position and saving into struct Some(position) => { - let (position_after_strategy, strategy_events, strategy_messages) = - self.strategy.on_new_tick(position, &self); + let mut events = None; + let mut messages = None; + + let (pos_on_tick, events_on_tick, messages_on_tick) = self + .strategy + .on_tick(position, self.current_tick(), &self.positions_history); + + let (pos_post_tick, events_post_tick, messages_post_tick) = self + .strategy + .post_tick(pos_on_tick, self.current_tick(), &self.positions_history); + + events.merge(events_on_tick); + events.merge(events_post_tick); + + messages.merge(messages_on_tick); + messages.merge(messages_post_tick); self.positions_history - .insert(self.current_tick(), position_after_strategy.clone()); - self.active_position = Some(position_after_strategy); + .insert(self.current_tick(), pos_post_tick.clone()); + self.active_position = Some(pos_post_tick); - return Ok((strategy_events, strategy_messages)); + return Ok((events, messages)); } } } @@ -501,12 +516,12 @@ impl OrderManager { for m in messages { match m { Message::SubmitOrder { order } => { - info!("Closing open order with a {} order", order.kind()); + info!("Closing open order."); + info!("Cancelling open order #{}", active_order.id); + self.client.cancel_order(active_order).await?; - if let Ok(_) = self.client.submit_order(&order).await { - info!("Cancelling open order #{}", active_order.id); - self.client.cancel_order(active_order).await?; - } + info!("Submitting {}...", order.kind()); + self.client.submit_order(&order).await?; } _ => { debug!("Received unsupported message from order strategy. Unimplemented.") @@ -581,6 +596,9 @@ impl PairManager { } pub async fn update_managers(&mut self, tick: u64) -> Result<(), BoxError> { + let mut events = None; + let mut messages = None; + let (price_results, pos_results, order_results) = tokio::join!( self.price_manager.update(tick), self.position_manager.update(tick), @@ -591,9 +609,16 @@ impl PairManager { let (opt_pos_events, opt_pos_messages) = pos_results?; let (opt_order_events, opt_order_messages) = order_results?; - // TODO: to move into Handler? + events.merge(opt_price_events); + events.merge(opt_pos_events); + events.merge(opt_order_events); - if let Some(messages) = opt_pos_messages { + messages.merge(opt_price_messages); + messages.merge(opt_pos_messages); + messages.merge(opt_order_messages); + + // TODO: to move into Handler? + if let Some(messages) = messages { for m in messages { match m { Message::ClosePosition { position_id } => { diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index e7c0890..14d79e3 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -4,13 +4,13 @@ use std::ops::Neg; use dyn_clone::DynClone; use log::{debug, info}; -use tokio::sync::oneshot; +use tokio::sync::{oneshot, RwLock}; use crate::events::{Event, EventKind, EventMetadata, Message}; use crate::managers::{OrderManager, PositionManager, TrackedPositionsMap}; use crate::models::{ ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionProfitState, - TradingPlatform, + PositionState, TradingPlatform, }; use crate::BoxError; @@ -20,10 +20,17 @@ use crate::BoxError; pub trait PositionStrategy: DynClone + Send + Sync { fn name(&self) -> String; - fn on_new_tick( - &self, + fn on_tick( + &mut self, position: Position, - manager: &PositionManager, + current_tick: u64, + positions_history: &HashMap, + ) -> (Position, Option>, Option>); + fn post_tick( + &mut self, + position: Position, + current_tick: u64, + positions_history: &HashMap, ) -> (Position, Option>, Option>); } @@ -65,10 +72,10 @@ pub struct TrailingStop { } impl TrailingStop { - const BREAK_EVEN_PERC: f64 = 0.2; - const MIN_PROFIT_PERC: f64 = TrailingStop::BREAK_EVEN_PERC + 0.3; + const BREAK_EVEN_PERC: f64 = 0.01; + const MIN_PROFIT_PERC: f64 = TrailingStop::BREAK_EVEN_PERC + 0.03; const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 2.5; - const MAX_LOSS_PERC: f64 = -0.01; + const MAX_LOSS_PERC: f64 = -4.0; const TAKER_FEE: f64 = 0.2; @@ -78,8 +85,36 @@ impl TrailingStop { } } - fn net_pl_percentage(pl: f64, fee: f64) -> f64 { - pl - fee + fn update_stop_percentage(&mut self, position: &Position) { + if let Some(profit_state) = position.profit_state() { + let profit_state_delta = match profit_state { + PositionProfitState::MinimumProfit => Some(Self::MIN_PROFIT_PERC), + PositionProfitState::Profit => Some(Self::GOOD_PROFIT_PERC), + _ => None, + }; + + if let Some(profit_state_delta) = profit_state_delta { + let current_stop_percentage = position.pl_perc() - profit_state_delta; + + match profit_state { + PositionProfitState::MinimumProfit | PositionProfitState::Profit => { + match self.stop_percentages.get(&position.id()) { + None => { + self.stop_percentages + .insert(position.id(), current_stop_percentage); + } + Some(existing_threshold) => { + if existing_threshold < ¤t_stop_percentage { + self.stop_percentages + .insert(position.id(), current_stop_percentage); + } + } + } + } + _ => {} + } + } + } } } @@ -88,14 +123,13 @@ impl PositionStrategy for TrailingStop { "Trailing stop".into() } - fn on_new_tick( - &self, + /// Sets the profit state of an open position + fn on_tick( + &mut self, position: Position, - manager: &PositionManager, + current_tick: u64, + positions_history: &HashMap, ) -> (Position, Option>, Option>) { - let mut messages = vec![]; - let events = vec![]; - let pl_perc = position.pl_perc(); let state = { @@ -110,35 +144,21 @@ impl PositionStrategy for TrailingStop { } else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 { PositionProfitState::Loss } else { - debug!("Inserting close position message..."); - messages.push(Message::ClosePosition { - position_id: position.id(), - }); PositionProfitState::Critical } }; - let opt_pre_pw = manager.position_previous_tick(position.id(), None); + let opt_prev_position = positions_history.get(&(current_tick - 1)); let event_metadata = EventMetadata::new(Some(position.id()), None); let new_position = position.clone().with_profit_state(Some(state)); - match opt_pre_pw { + match opt_prev_position { Some(prev) => { if prev.profit_state() == Some(state) { - return ( - new_position, - (!events.is_empty()).then_some(events), - (!messages.is_empty()).then_some(messages), - ); + return (new_position, None, None); } } - None => { - return ( - new_position, - (!events.is_empty()).then_some(events), - (!messages.is_empty()).then_some(messages), - ) - } + None => return (new_position, None, None), }; let events = { @@ -147,31 +167,31 @@ impl PositionStrategy for TrailingStop { if state == PositionProfitState::Profit { events.push(Event::new( EventKind::ReachedGoodProfit, - manager.current_tick(), + current_tick, Some(event_metadata), )); } else if state == PositionProfitState::MinimumProfit { events.push(Event::new( EventKind::ReachedMinProfit, - manager.current_tick(), + current_tick, Some(event_metadata), )); } else if state == PositionProfitState::BreakEven { events.push(Event::new( EventKind::ReachedBreakEven, - manager.current_tick(), + current_tick, Some(event_metadata), )); } else if state == PositionProfitState::Loss { events.push(Event::new( EventKind::ReachedLoss, - manager.current_tick(), + current_tick, Some(event_metadata), )); } else { events.push(Event::new( EventKind::ReachedMaxLoss, - manager.current_tick(), + current_tick, Some(event_metadata), )); } @@ -179,11 +199,41 @@ impl PositionStrategy for TrailingStop { events }; - return ( - new_position, - (!events.is_empty()).then_some(events), - (!messages.is_empty()).then_some(messages), - ); + return (new_position, Some(events), None); + } + + fn post_tick( + &mut self, + position: Position, + _: u64, + _: &HashMap, + ) -> (Position, Option>, Option>) { + let close_message = Message::ClosePosition { + position_id: position.id(), + }; + + // if critical, early return with close position + if let Some(profit_state) = position.profit_state() { + match profit_state { + PositionProfitState::Critical => { + return (position, None, Some(vec![close_message])) + } + _ => {} + } + }; + + // let's check if we surpassed an existing stop percentage + if let Some(existing_stop_percentage) = self.stop_percentages.get(&position.id()) { + if existing_stop_percentage < &position.pl_perc() { + return (position, None, Some(vec![close_message])); + } + } + + self.update_stop_percentage(&position); + + println!("Stop percentages: {:?}", self.stop_percentages); + + (position, None, None) } } -- 2.47.2 From 7d639d1b4ccef3ebb011b4b80cd489a8fbb8adc3 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sun, 24 Jan 2021 20:50:19 +0000 Subject: [PATCH 081/127] renamed orderstrategy methods, debug in position manager --- rustybot/src/strategy.rs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 14d79e3..bcf8b30 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -45,10 +45,10 @@ pub trait OrderStrategy: DynClone + Send + Sync { fn name(&self) -> String; /// This method is called when the OrderManager checks the open orders on a new tick. /// It should manage if some orders have to be closed or keep open. - fn on_update(&self); + fn on_open_order(&self); /// This method is called when the OrderManager is requested to close /// a position that has an open order associated to it. - fn on_position_close( + fn on_position_order( &self, order: &ActiveOrder, open_position: &Position, @@ -75,7 +75,7 @@ impl TrailingStop { const BREAK_EVEN_PERC: f64 = 0.01; const MIN_PROFIT_PERC: f64 = TrailingStop::BREAK_EVEN_PERC + 0.03; const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 2.5; - const MAX_LOSS_PERC: f64 = -4.0; + const MAX_LOSS_PERC: f64 = -0.5; const TAKER_FEE: f64 = 0.2; @@ -86,6 +86,12 @@ impl TrailingStop { } fn update_stop_percentage(&mut self, position: &Position) { + println!( + "State: {:?} | PL%: {:0.2}", + position.profit_state(), + position.pl_perc() + ); + if let Some(profit_state) = position.profit_state() { let profit_state_delta = match profit_state { PositionProfitState::MinimumProfit => Some(Self::MIN_PROFIT_PERC), @@ -94,6 +100,12 @@ impl TrailingStop { }; if let Some(profit_state_delta) = profit_state_delta { + println!( + "PL%: {} | Delta: {}", + position.pl_perc(), + profit_state_delta + ); + let current_stop_percentage = position.pl_perc() - profit_state_delta; match profit_state { @@ -224,7 +236,7 @@ impl PositionStrategy for TrailingStop { // let's check if we surpassed an existing stop percentage if let Some(existing_stop_percentage) = self.stop_percentages.get(&position.id()) { - if existing_stop_percentage < &position.pl_perc() { + if existing_stop_percentage <= &position.pl_perc() { return (position, None, Some(vec![close_message])); } } @@ -261,14 +273,14 @@ impl OrderStrategy for FastOrderStrategy { "Fast order strategy".into() } - fn on_update(&self) { + fn on_open_order(&self) { unimplemented!() } - fn on_position_close( + fn on_position_order( &self, order: &ActiveOrder, - active_position: &Position, + _: &Position, order_book: &OrderBook, ) -> Result<(Option>, Option>), BoxError> { let mut messages = vec![]; -- 2.47.2 From 64a687445df77ffd5a1e6b219e8d97f7e53c511e Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sun, 24 Jan 2021 20:50:54 +0000 Subject: [PATCH 082/127] started moving logic from close position to update in order manager --- rustybot/src/managers.rs | 39 +++++++++++++-------------------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 79b6133..7ebdfdf 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -3,8 +3,10 @@ use std::ops::{Deref, Neg}; use bitfinex::ticker::TradingPairTicker; use futures_util::stream::FuturesUnordered; -use log::{debug, error, info}; +use log::{debug, error, info, trace}; +use merge::Merge; use tokio::signal::unix::Signal; +use tokio::stream::StreamExt; use tokio::sync::mpsc::channel; use tokio::sync::mpsc::{Receiver, Sender}; use tokio::sync::{oneshot, RwLock}; @@ -17,15 +19,9 @@ use crate::models::{ }; use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop}; use crate::BoxError; -use merge::Merge; -use tokio::stream::StreamExt; pub type OptionUpdate = (Option>, Option>); -pub struct EventManager { - events: Vec, -} - /****************** * PRICES ******************/ @@ -108,20 +104,9 @@ impl PriceManager { current_prices, self.pair.clone(), None, - // None, )) } - // fn add_event(&mut self, event: Event) { - // self.events.push(event); - // - // self.dispatcher.call_event_handlers(&event, &self); - // } - // - // fn add_signal(&mut self, signal: SignalKind) { - // self.signals.insert(self.current_tick(), signal); - // } - pub fn pair(&self) -> &SymbolPair { &self.pair } @@ -133,7 +118,6 @@ pub struct PriceEntry { pair: SymbolPair, price: PriceTicker, events: Option>, - // signals: Option>, } impl PriceEntry { @@ -142,14 +126,12 @@ impl PriceEntry { price: PriceTicker, pair: SymbolPair, events: Option>, - // signals: Option>, ) -> Self { PriceEntry { tick, pair, price, events, - // signals, } } @@ -165,9 +147,6 @@ impl PriceEntry { pub fn events(&self) -> &Option> { &self.events } - // pub fn signals(&self) -> &Option> { - // &self.signals - // } } /****************** @@ -510,7 +489,7 @@ impl OrderManager { let (_, strat_messages) = self.strategy - .on_position_close(active_order, &position, &order_book)?; + .on_position_order(active_order, &position, &order_book)?; if let Some(messages) = strat_messages { for m in messages { @@ -535,8 +514,16 @@ impl OrderManager { Ok(()) } + // TODO: finish me pub async fn update(&self) -> Result { - // TODO: implement me + trace!("\tUpdating {} order manager.", self.pair); + + let (open_orders, opt_open_positions) = tokio::join!( + self.client.active_orders(&self.pair), + self.client.active_positions(&self.pair) + ); + let (open_orders, opt_open_positions) = (open_orders?, opt_open_positions?); + Ok((None, None)) } -- 2.47.2 From 7de2a6ad77f1f4551047da63aaf01b42f13d2dcb Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sun, 24 Jan 2021 20:53:33 +0000 Subject: [PATCH 083/127] cargo fix --- rustybot/src/bot.rs | 9 +++------ rustybot/src/connectors.rs | 4 +--- rustybot/src/currency.rs | 3 +-- rustybot/src/events.rs | 10 +++------- rustybot/src/managers.rs | 12 ++++-------- rustybot/src/models.rs | 8 +++----- rustybot/src/strategy.rs | 6 +----- 7 files changed, 16 insertions(+), 36 deletions(-) diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index b332147..ca30b78 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -1,14 +1,11 @@ use core::time::Duration; -use std::collections::HashMap; -use log::{debug, error, info}; +use log::{error, info}; use tokio::time::delay_for; -use crate::connectors::{Client, ExchangeDetails}; +use crate::connectors::ExchangeDetails; use crate::currency::{Symbol, SymbolPair}; -use crate::events::Event; -use crate::managers::{ExchangeManager, OrderManager, PositionManager, PriceManager}; -use crate::strategy::PositionStrategy; +use crate::managers::ExchangeManager; use crate::ticker::Ticker; use crate::BoxError; diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 8fbdff9..355cd20 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -7,8 +7,6 @@ use async_trait::async_trait; use bitfinex::api::Bitfinex; use bitfinex::orders::{CancelOrderForm, OrderMeta, OrderResponse}; use bitfinex::ticker::TradingPairTicker; -use chrono::{DateTime, NaiveDate, NaiveDateTime, Utc}; -use log::debug; use crate::currency::SymbolPair; use crate::models::{ @@ -64,7 +62,7 @@ impl Client { let (best_ask, best_bid) = (order_book.lowest_ask(), order_book.highest_bid()); // updating positions with effective profit/loss - positions.iter_mut().flatten().for_each(|mut x| { + positions.iter_mut().flatten().for_each(|x| { if x.is_short() { x.update_profit_loss(best_ask, 0.2); } else { diff --git a/rustybot/src/currency.rs b/rustybot/src/currency.rs index ae4a618..0798c87 100644 --- a/rustybot/src/currency.rs +++ b/rustybot/src/currency.rs @@ -1,12 +1,11 @@ use core::fmt; use std::borrow::Cow; -use std::convert::TryFrom; use std::fmt::{Display, Formatter}; +use std::str::FromStr; use regex::Regex; use crate::BoxError; -use std::str::FromStr; #[derive(Clone, PartialEq, Hash, Debug, Eq)] pub struct Symbol { diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 57c5968..085b3f8 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -1,12 +1,8 @@ -use std::collections::HashMap; -use std::future::Future; - -use tokio::task::JoinHandle; - -use crate::managers::{OptionUpdate, OrderManager, PositionManager, PriceManager}; -use crate::models::{OrderForm, OrderKind, Position, PositionProfitState}; use tokio::sync::oneshot; +use crate::managers::OptionUpdate; +use crate::models::OrderForm; + #[derive(Debug)] pub struct ActorMessage { pub(crate) message: Message, diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 7ebdfdf..165d394 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -1,15 +1,13 @@ use std::collections::HashMap; -use std::ops::{Deref, Neg}; +use std::ops::Neg; -use bitfinex::ticker::TradingPairTicker; use futures_util::stream::FuturesUnordered; use log::{debug, error, info, trace}; use merge::Merge; -use tokio::signal::unix::Signal; use tokio::stream::StreamExt; use tokio::sync::mpsc::channel; use tokio::sync::mpsc::{Receiver, Sender}; -use tokio::sync::{oneshot, RwLock}; +use tokio::sync::oneshot; use crate::connectors::{Client, ExchangeDetails}; use crate::currency::SymbolPair; @@ -395,9 +393,7 @@ impl OrderManager { Message::Update { .. } => { self.update().await?; } - Message::ClosePosition { - position_id: position_id, - } => self.close_position(position_id).await?, + Message::ClosePosition { position_id } => self.close_position(position_id).await?, _ => {} }; @@ -522,7 +518,7 @@ impl OrderManager { self.client.active_orders(&self.pair), self.client.active_positions(&self.pair) ); - let (open_orders, opt_open_positions) = (open_orders?, opt_open_positions?); + let (_open_orders, _opt_open_positions) = (open_orders?, opt_open_positions?); Ok((None, None)) } diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index 70e0cfe..3285dab 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -2,12 +2,8 @@ use std::fmt; use std::fmt::{Display, Formatter}; use std::hash::{Hash, Hasher}; -use chrono::{DateTime, TimeZone}; -use float_cmp::ApproxEq; - -use crate::connectors::{Exchange, ExchangeDetails}; +use crate::connectors::Exchange; use crate::currency::SymbolPair; -use crate::BoxError; /*************** * Prices @@ -190,6 +186,7 @@ pub enum OrderKind { amount: f64, }, } + impl OrderKind { pub fn as_str(&self) -> &'static str { match self { @@ -460,6 +457,7 @@ impl PartialEq for Position { self.id() == other.id() } } + impl Eq for Position {} #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index bcf8b30..b93b151 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -1,13 +1,9 @@ use std::collections::HashMap; use std::fmt::{Debug, Formatter}; -use std::ops::Neg; use dyn_clone::DynClone; -use log::{debug, info}; -use tokio::sync::{oneshot, RwLock}; use crate::events::{Event, EventKind, EventMetadata, Message}; -use crate::managers::{OrderManager, PositionManager, TrackedPositionsMap}; use crate::models::{ ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionProfitState, PositionState, TradingPlatform, @@ -228,7 +224,7 @@ impl PositionStrategy for TrailingStop { if let Some(profit_state) = position.profit_state() { match profit_state { PositionProfitState::Critical => { - return (position, None, Some(vec![close_message])) + return (position, None, Some(vec![close_message])); } _ => {} } -- 2.47.2 From bb5d1328d62b7f52d57a63cb8bc8fe864eb5d38c Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sun, 24 Jan 2021 21:12:06 +0000 Subject: [PATCH 084/127] general cleanup --- rustybot/src/bot.rs | 4 -- rustybot/src/connectors.rs | 5 +- rustybot/src/events.rs | 121 ------------------------------------- rustybot/src/managers.rs | 20 +++--- rustybot/src/strategy.rs | 2 - rustybot/src/ticker.rs | 8 +-- 6 files changed, 14 insertions(+), 146 deletions(-) diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index ca30b78..cc57e42 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -11,8 +11,6 @@ use crate::BoxError; pub struct BfxBot { ticker: Ticker, - quote: Symbol, - trading_symbols: Vec, exchange_managers: Vec, } @@ -35,8 +33,6 @@ impl BfxBot { BfxBot { ticker: Ticker::new(tick_duration), - quote, - trading_symbols, exchange_managers, } } diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 355cd20..8ad4e46 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -62,6 +62,7 @@ impl Client { let (best_ask, best_bid) = (order_book.lowest_ask(), order_book.highest_bid()); // updating positions with effective profit/loss + // TODO: change fee with account's taker fee positions.iter_mut().flatten().for_each(|x| { if x.is_short() { x.update_profit_loss(best_ask, 0.2); @@ -117,9 +118,6 @@ impl Debug for dyn Connector { pub struct BitfinexConnector { bfx: Bitfinex, - affiliate_code: Option, - // account_info: String, - // ledger: String, } impl BitfinexConnector { @@ -128,7 +126,6 @@ impl BitfinexConnector { pub fn new(api_key: &str, api_secret: &str) -> Self { BitfinexConnector { bfx: Bitfinex::new(Some(api_key.into()), Some(api_secret.into())), - affiliate_code: Some(BitfinexConnector::AFFILIATE_CODE.into()), } } diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 085b3f8..a8f029c 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -44,7 +44,6 @@ pub enum EventKind { TrailingStopMoved, OrderSubmitted, NewTick, - Any, } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] @@ -66,124 +65,4 @@ impl Event { fn has_metadata(&self) -> bool { self.metadata.is_some() } - - pub fn kind(&self) -> EventKind { - self.kind - } - pub fn tick(&self) -> u64 { - self.tick - } - pub fn metadata(&self) -> Option { - self.metadata - } } -// -// pub struct Dispatcher { -// event_handlers: HashMap JoinHandle<()>>>>, -// profit_state_handlers: -// HashMap JoinHandle<()>>>>, -// signal_handlers: HashMap JoinHandle<()>>>>, -// -// on_any_event_handlers: Vec JoinHandle<()>>>, -// on_any_profit_state_handlers: Vec JoinHandle<()>>>, -// } -// -// impl Dispatcher { -// pub fn new() -> Self { -// Dispatcher { -// event_handlers: HashMap::new(), -// profit_state_handlers: HashMap::new(), -// signal_handlers: HashMap::new(), -// on_any_event_handlers: Vec::new(), -// on_any_profit_state_handlers: Vec::new(), -// } -// } -// -// pub fn call_signal_handlers(&self, signal: &SignalKind) { -// if let Some(functions) = self.signal_handlers.get(&signal) { -// for f in functions { -// f(signal); -// } -// } -// } -// -// pub fn call_event_handlers(&self, event: &Event, status: &PriceManager) { -// if let Some(functions) = self.event_handlers.get(&event.kind()) { -// for f in functions { -// f(event, status); -// } -// } -// -// for f in &self.on_any_event_handlers { -// f(event, status); -// } -// } -// -// pub fn call_position_state_handlers(&self, position: &Position, status: &PriceManager) { -// if let Some(profit_state) = &position.profit_state() { -// if let Some(functions) = self.profit_state_handlers.get(profit_state) { -// for f in functions { -// f(position, status); -// } -// } -// } -// -// for f in &self.on_any_profit_state_handlers { -// f(position, status); -// } -// } -// -// pub fn register_event_handler(&mut self, event: EventKind, f: F) -// where -// F: Fn(&Event, &PriceManager) -> Fut, -// Fut: Future + Send, -// { -// match event { -// EventKind::Any => self -// .on_any_event_handlers -// .push(Box::new(move |e, s| tokio::spawn(f(&e, s)))), -// _ => self -// .event_handlers -// .entry(event) -// .or_default() -// .push(Box::new(move |e, s| tokio::spawn(f(&e, s)))), -// } -// } -// -// pub fn register_positionstate_handler( -// &mut self, -// state: PositionProfitState, -// f: F, -// ) where -// F: Fn(&Position, &PriceManager) -> Fut, -// Fut: Future + Send, -// { -// match state { -// // PositionProfitState::Any => self -// // .on_any_position_state_handlers -// // .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))), -// _ => self -// .profit_state_handlers -// .entry(state) -// .or_default() -// .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))), -// } -// } -// -// pub fn register_signal_handler(&mut self, signal: SignalKind, f: F) -// where -// F: Fn(&SignalKind) -> Fut, -// Fut: Future + Send, -// { -// match signal { -// // PositionProfitState::Any => self -// // .on_any_position_state_handlers -// // .push(Box::new(move |p, s| tokio::spawn(f(&p, s)))), -// _ => self -// .signal_handlers -// .entry(signal) -// .or_default() -// .push(Box::new(move |s| tokio::spawn(f(s)))), -// } -// } -// } diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 165d394..f3c7131 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -85,9 +85,10 @@ impl PriceManager { _ => {} } - message.respond_to.send((None, None)); - - Ok(()) + Ok(message + .respond_to + .send((None, None)) + .map_err(|_| BoxError::from("Could not send message."))?) } pub fn add_entry(&mut self, entry: PriceEntry) { @@ -224,9 +225,11 @@ impl PositionManager { pub async fn handle_message(&mut self, msg: ActorMessage) -> Result<(), BoxError> { match msg.message { Message::Update { tick } => { - let result = self.update(tick).await?; + self.update(tick).await?; - msg.respond_to.send(result); + msg.respond_to + .send((None, None)) + .map_err(|_| BoxError::from("Could not send message."))?; } _ => {} }; @@ -397,9 +400,10 @@ impl OrderManager { _ => {} }; - msg.respond_to.send((None, None)); - - Ok(()) + Ok(msg + .respond_to + .send((None, None)) + .map_err(|_| BoxError::from("Could not send message."))?) } pub async fn close_position(&mut self, position_id: u64) -> Result<(), BoxError> { diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index b93b151..1407439 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -73,8 +73,6 @@ impl TrailingStop { const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 2.5; const MAX_LOSS_PERC: f64 = -0.5; - const TAKER_FEE: f64 = 0.2; - pub fn new() -> Self { TrailingStop { stop_percentages: HashMap::new(), diff --git a/rustybot/src/ticker.rs b/rustybot/src/ticker.rs index 6a4d4c0..b5c036e 100644 --- a/rustybot/src/ticker.rs +++ b/rustybot/src/ticker.rs @@ -1,8 +1,7 @@ -use tokio::time::{Duration, Instant}; +use tokio::time::{Duration}; pub struct Ticker { duration: Duration, - start_time: Instant, current_tick: u64, } @@ -10,7 +9,6 @@ impl Ticker { pub fn new(duration: Duration) -> Self { Ticker { duration, - start_time: Instant::now(), current_tick: 1, } } @@ -18,13 +16,9 @@ impl Ticker { pub fn inc(&mut self) { self.current_tick += 1 } - pub fn duration(&self) -> Duration { self.duration } - pub fn start_time(&self) -> Instant { - self.start_time - } pub fn current_tick(&self) -> u64 { self.current_tick } -- 2.47.2 From b02554778e749bc8dc1247f8a0d9d3f0180c8b1e Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 25 Jan 2021 12:13:49 +0000 Subject: [PATCH 085/127] connected actor messages results with actual actor results (duh!) --- rustybot/src/managers.rs | 45 +++++++++++++++++++--------------------- rustybot/src/strategy.rs | 21 ++++++++++++------- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index f3c7131..127dc24 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -223,18 +223,15 @@ impl PositionManager { } pub async fn handle_message(&mut self, msg: ActorMessage) -> Result<(), BoxError> { - match msg.message { - Message::Update { tick } => { - self.update(tick).await?; - - msg.respond_to - .send((None, None)) - .map_err(|_| BoxError::from("Could not send message."))?; - } - _ => {} + let (events, messages) = match msg.message { + Message::Update { tick } => self.update(tick).await?, + _ => (None, None), }; - Ok(()) + Ok(msg + .respond_to + .send((events, messages)) + .map_err(|_| BoxError::from("Could not send message."))?) } pub async fn update(&mut self, tick: u64) -> Result { @@ -284,6 +281,7 @@ impl PositionManager { .insert(self.current_tick(), pos_post_tick.clone()); self.active_position = Some(pos_post_tick); + println!("Message: {:?}", messages); return Ok((events, messages)); } } @@ -392,21 +390,19 @@ impl OrderManager { } pub async fn handle_message(&mut self, msg: ActorMessage) -> Result<(), BoxError> { - match msg.message { - Message::Update { .. } => { - self.update().await?; - } + let (events, messages) = match msg.message { + Message::Update { .. } => self.update().await?, Message::ClosePosition { position_id } => self.close_position(position_id).await?, - _ => {} + _ => (None, None), }; Ok(msg .respond_to - .send((None, None)) + .send((events, messages)) .map_err(|_| BoxError::from("Could not send message."))?) } - pub async fn close_position(&mut self, position_id: u64) -> Result<(), BoxError> { + pub async fn close_position(&mut self, position_id: u64) -> Result { info!("Closing position #{}", position_id); debug!("Retrieving open orders, positions and current prices..."); @@ -511,18 +507,18 @@ impl OrderManager { } } - Ok(()) + Ok((None, None)) } // TODO: finish me pub async fn update(&self) -> Result { trace!("\tUpdating {} order manager.", self.pair); - - let (open_orders, opt_open_positions) = tokio::join!( - self.client.active_orders(&self.pair), - self.client.active_positions(&self.pair) - ); - let (_open_orders, _opt_open_positions) = (open_orders?, opt_open_positions?); + // + // let (open_orders, opt_open_positions) = tokio::join!( + // self.client.active_orders(&self.pair), + // self.client.active_positions(&self.pair) + // ); + // let (_open_orders, _opt_open_positions) = (open_orders?, opt_open_positions?); Ok((None, None)) } @@ -604,6 +600,7 @@ impl PairManager { messages.merge(opt_pos_messages); messages.merge(opt_order_messages); + println!("Total messages: {:?}", messages); // TODO: to move into Handler? if let Some(messages) = messages { for m in messages { diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 1407439..c9edd88 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::fmt::{Debug, Formatter}; use dyn_clone::DynClone; +use log::info; use crate::events::{Event, EventKind, EventMetadata, Message}; use crate::models::{ @@ -68,10 +69,10 @@ pub struct TrailingStop { } impl TrailingStop { - const BREAK_EVEN_PERC: f64 = 0.01; - const MIN_PROFIT_PERC: f64 = TrailingStop::BREAK_EVEN_PERC + 0.03; + const BREAK_EVEN_PERC: f64 = 0.1; + const MIN_PROFIT_PERC: f64 = TrailingStop::BREAK_EVEN_PERC + 0.2; const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 2.5; - const MAX_LOSS_PERC: f64 = -0.5; + const MAX_LOSS_PERC: f64 = -2.25; pub fn new() -> Self { TrailingStop { @@ -88,14 +89,14 @@ impl TrailingStop { if let Some(profit_state) = position.profit_state() { let profit_state_delta = match profit_state { - PositionProfitState::MinimumProfit => Some(Self::MIN_PROFIT_PERC), - PositionProfitState::Profit => Some(Self::GOOD_PROFIT_PERC), + PositionProfitState::MinimumProfit => Some(0.2), + PositionProfitState::Profit => Some(0.1), _ => None, }; if let Some(profit_state_delta) = profit_state_delta { println!( - "PL%: {} | Delta: {}", + "PL%: {:0.2} | Delta: {}", position.pl_perc(), profit_state_delta ); @@ -230,14 +231,18 @@ impl PositionStrategy for TrailingStop { // let's check if we surpassed an existing stop percentage if let Some(existing_stop_percentage) = self.stop_percentages.get(&position.id()) { - if existing_stop_percentage <= &position.pl_perc() { + if &position.pl_perc() <= existing_stop_percentage { + info!("Stop percentage surpassed. Closing position."); return (position, None, Some(vec![close_message])); } } self.update_stop_percentage(&position); - println!("Stop percentages: {:?}", self.stop_percentages); + println!( + "Stop percentage: {:0.2}", + self.stop_percentages.get(&position.id()).unwrap() + ); (position, None, None) } -- 2.47.2 From 3a38c20f56fba2a248ab3bbc5be913680c82de23 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 25 Jan 2021 12:28:01 +0000 Subject: [PATCH 086/127] fixed sign calculation of pl_perc --- rustybot/src/models.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index 3285dab..9c12914 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -393,10 +393,11 @@ impl Position { best_offer - base_price }; + let profit_loss = delta * self.amount; let profit_loss_percentage = - ((1.0 - (base_price + delta) / base_price) * 100.0).abs() * delta.signum(); + ((1.0 - (base_price + delta) / base_price) * 100.0).abs() * profit_loss.signum(); - self.pl = delta * self.amount; + self.pl = profit_loss; self.pl_perc = profit_loss_percentage; } -- 2.47.2 From a2eae0ac13b138d63cdada81ef8807215d589f97 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 25 Jan 2021 13:16:45 +0000 Subject: [PATCH 087/127] damn calculations --- rustybot/src/models.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index 9c12914..a5fb122 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -384,16 +384,21 @@ impl Position { } pub fn update_profit_loss(&mut self, best_offer: f64, fee_perc: f64) { - let best_offer = best_offer.abs(); - let base_price = self.base_price * (1.0 + fee_perc / 100.0); + let (base_price, delta) = { + if self.is_short() { + let base_price = self.base_price * (1.0 - fee_perc / 100.0); + let delta = base_price - best_offer; - let delta = if self.is_short() { - base_price - best_offer - } else { - best_offer - base_price + (base_price, delta) + } else { + let base_price = self.base_price * (1.0 - fee_perc / 100.0); + let delta = best_offer - base_price; + + (base_price, delta) + } }; - let profit_loss = delta * self.amount; + let profit_loss = delta * self.amount.abs(); let profit_loss_percentage = ((1.0 - (base_price + delta) / base_price) * 100.0).abs() * profit_loss.signum(); -- 2.47.2 From d51facc0b2f502e878d2bd815cd10a788d66292e Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 25 Jan 2021 13:17:13 +0000 Subject: [PATCH 088/127] added multiple api keys to handle asynchronous signed requests (conflicting nonces) --- rustybot/src/connectors.rs | 57 +++++++++++++++++++++++++------------- rustybot/src/strategy.rs | 5 +--- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 8ad4e46..2a74e83 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -22,29 +22,48 @@ pub enum Exchange { #[derive(Eq, PartialEq, Hash, Clone, Debug)] pub enum ExchangeDetails { - Bitfinex { api_key: String, api_secret: String }, + Bitfinex { + prices_api_key: String, + prices_api_secret: String, + orders_api_key: String, + orders_api_secret: String, + positions_api_key: String, + positions_api_secret: String, + }, } /// You do **not** have to wrap the `Client` in an [`Rc`] or [`Arc`] to **reuse** it, /// because it already uses an [`Arc`] internally. #[derive(Clone, Debug)] pub struct Client { - exchange: ExchangeDetails, - inner: Arc>, + exchange: Exchange, + prices_connector: Arc>, + orders_connector: Arc>, + positions_connector: Arc>, } impl Client { pub fn new(exchange: &ExchangeDetails) -> Self { - let inner = match &exchange { + match exchange { ExchangeDetails::Bitfinex { - api_key, - api_secret, - } => BitfinexConnector::new(&api_key, &api_secret), - }; - - Client { - exchange: exchange.clone(), - inner: Arc::new(Box::new(inner)), + prices_api_key, + prices_api_secret, + orders_api_key, + orders_api_secret, + positions_api_key, + positions_api_secret, + } => Self { + exchange: Exchange::Bitfinex, + prices_connector: Arc::new(Box::new( + (BitfinexConnector::new(prices_api_key, prices_api_secret)), + )), + orders_connector: Arc::new(Box::new( + (BitfinexConnector::new(orders_api_key, orders_api_secret)), + )), + positions_connector: Arc::new(Box::new( + (BitfinexConnector::new(positions_api_key, positions_api_secret)), + )), + }, } } @@ -54,8 +73,8 @@ impl Client { ) -> Result>, BoxError> { // retrieving open positions and order book to calculate effective profit/loss let (positions, order_book) = tokio::join!( - self.inner.active_positions(pair), - self.inner.order_book(pair) + self.positions_connector.active_positions(pair), + self.orders_connector.order_book(pair) ); let (mut positions, order_book) = (positions?, order_book?); @@ -75,23 +94,23 @@ impl Client { } pub async fn current_prices(&self, pair: &SymbolPair) -> Result { - self.inner.current_prices(pair).await + self.prices_connector.current_prices(pair).await } pub async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError> { - self.inner.active_orders(pair).await + self.orders_connector.active_orders(pair).await } pub async fn submit_order(&self, order: &OrderForm) -> Result { - self.inner.submit_order(order).await + self.orders_connector.submit_order(order).await } pub async fn order_book(&self, pair: &SymbolPair) -> Result { - self.inner.order_book(pair).await + self.orders_connector.order_book(pair).await } pub async fn cancel_order(&self, order: &ActiveOrder) -> Result { - self.inner.cancel_order(order).await + self.orders_connector.cancel_order(order).await } } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index c9edd88..923a67d 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -239,10 +239,7 @@ impl PositionStrategy for TrailingStop { self.update_stop_percentage(&position); - println!( - "Stop percentage: {:0.2}", - self.stop_percentages.get(&position.id()).unwrap() - ); + println!("Stop percentage: {:?}", self.stop_percentages); (position, None, None) } -- 2.47.2 From ff17972f5be97df4f216e4b75c7910978a981284 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 25 Jan 2021 13:19:40 +0000 Subject: [PATCH 089/127] cleaned debug messages --- rustybot/src/managers.rs | 2 -- rustybot/src/strategy.rs | 8 +++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 127dc24..7cf202a 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -281,7 +281,6 @@ impl PositionManager { .insert(self.current_tick(), pos_post_tick.clone()); self.active_position = Some(pos_post_tick); - println!("Message: {:?}", messages); return Ok((events, messages)); } } @@ -600,7 +599,6 @@ impl PairManager { messages.merge(opt_pos_messages); messages.merge(opt_order_messages); - println!("Total messages: {:?}", messages); // TODO: to move into Handler? if let Some(messages) = messages { for m in messages { diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 923a67d..b694723 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -81,9 +81,9 @@ impl TrailingStop { } fn update_stop_percentage(&mut self, position: &Position) { - println!( - "State: {:?} | PL%: {:0.2}", - position.profit_state(), + info!( + "\tState: {:?} | PL%: {:0.2}", + position.profit_state().unwrap(), position.pl_perc() ); @@ -239,8 +239,6 @@ impl PositionStrategy for TrailingStop { self.update_stop_percentage(&position); - println!("Stop percentage: {:?}", self.stop_percentages); - (position, None, None) } } -- 2.47.2 From c4c87ed47ba2d19a84cf0ec6fe5bff4d4c6138c2 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 25 Jan 2021 13:54:25 +0000 Subject: [PATCH 090/127] damn calculations... --- rustybot/src/models.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index a5fb122..04e833b 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -391,7 +391,7 @@ impl Position { (base_price, delta) } else { - let base_price = self.base_price * (1.0 - fee_perc / 100.0); + let base_price = self.base_price * (1.0 + fee_perc / 100.0); let delta = best_offer - base_price; (base_price, delta) @@ -399,8 +399,7 @@ impl Position { }; let profit_loss = delta * self.amount.abs(); - let profit_loss_percentage = - ((1.0 - (base_price + delta) / base_price) * 100.0).abs() * profit_loss.signum(); + let profit_loss_percentage = delta / base_price * 100.0; self.pl = profit_loss; self.pl_perc = profit_loss_percentage; -- 2.47.2 From 32419952a82ff498dc5735c7dce548898d4fee6d Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 25 Jan 2021 16:21:37 +0000 Subject: [PATCH 091/127] tokio 1 --- rustybot/Cargo.lock | 454 ++++++++++++++++++++------------------- rustybot/Cargo.toml | 2 +- rustybot/src/bot.rs | 4 +- rustybot/src/managers.rs | 2 +- rustybot/src/strategy.rs | 2 +- 5 files changed, 234 insertions(+), 230 deletions(-) diff --git a/rustybot/Cargo.lock b/rustybot/Cargo.lock index 3c7e15c..c3f0e6a 100644 --- a/rustybot/Cargo.lock +++ b/rustybot/Cargo.lock @@ -24,6 +24,27 @@ dependencies = [ "memchr", ] +[[package]] +name = "async-stream" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3670df70cbc01729f901f94c887814b3c68db038aad1329a418bae178bc5295c" +dependencies = [ + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3548b8efc9f8e8a5a0a2808c5bd8451a9031b9e5b879a79590304ae928b0a70" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-trait" version = "0.1.42" @@ -43,7 +64,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -93,11 +114,13 @@ dependencies = [ "error-chain", "hex", "log 0.3.9", + "rand 0.8.2", "reqwest", "ring", "serde", "serde_derive", "serde_json", + "tokio 1.1.0", "tungstenite 0.9.2", "url", ] @@ -178,6 +201,12 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0dcbc35f504eb6fc275a6d20e4ebcda18cf50d40ba6fabff8c711fa16cb3b16" +[[package]] +name = "bytes" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" + [[package]] name = "cc" version = "1.0.66" @@ -206,7 +235,7 @@ dependencies = [ "num-integer", "num-traits", "time", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -217,7 +246,7 @@ checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59" dependencies = [ "atty", "lazy_static", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -341,22 +370,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -dependencies = [ - "bitflags", - "fuchsia-zircon-sys", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" - [[package]] name = "futures-channel" version = "0.3.8" @@ -431,6 +444,17 @@ dependencies = [ "wasi 0.9.0+wasi-snapshot-preview1", ] +[[package]] +name = "getrandom" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", +] + [[package]] name = "gimli" version = "0.23.0" @@ -439,11 +463,11 @@ checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" [[package]] name = "h2" -version = "0.2.7" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535" +checksum = "6b67e66362108efccd8ac053abafc8b7a8d86a37e6e48fc4f6f7485eb5e9e6a5" dependencies = [ - "bytes 0.5.6", + "bytes 1.0.1", "fnv", "futures-core", "futures-sink", @@ -451,7 +475,7 @@ dependencies = [ "http 0.2.2", "indexmap", "slab", - "tokio 0.2.24", + "tokio 1.1.0", "tokio-util", "tracing", "tracing-futures", @@ -502,11 +526,11 @@ dependencies = [ [[package]] name = "http-body" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" +checksum = "2861bd27ee074e5ee891e8b539837a9430012e249d7f0ca2d795650f579c1994" dependencies = [ - "bytes 0.5.6", + "bytes 1.0.1", "http 0.2.2", ] @@ -524,11 +548,11 @@ checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" [[package]] name = "hyper" -version = "0.13.9" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ad767baac13b44d4529fcf58ba2cd0995e36e7b435bc5b039de6f47e880dbf" +checksum = "12219dc884514cb4a6a03737f4413c0e01c23a1b059b0156004b23f1e19dccbe" dependencies = [ - "bytes 0.5.6", + "bytes 1.0.1", "futures-channel", "futures-core", "futures-util", @@ -540,7 +564,7 @@ dependencies = [ "itoa", "pin-project 1.0.2", "socket2", - "tokio 0.2.24", + "tokio 1.1.0", "tower-service", "tracing", "want", @@ -548,15 +572,15 @@ dependencies = [ [[package]] name = "hyper-tls" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d979acc56dcb5b8dddba3917601745e877576475aa046df3226eabdecef78eed" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 0.5.6", + "bytes 1.0.1", "hyper", "native-tls", - "tokio 0.2.24", - "tokio-tls", + "tokio 1.1.0", + "tokio-native-tls", ] [[package]] @@ -598,6 +622,15 @@ dependencies = [ "bytes 0.5.6", ] +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "iovec" version = "0.1.4" @@ -628,16 +661,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -650,6 +673,15 @@ version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" +[[package]] +name = "lock_api" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.3.9" @@ -708,16 +740,6 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" -[[package]] -name = "mime_guess" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" -dependencies = [ - "mime", - "unicase", -] - [[package]] name = "miniz_oxide" version = "0.4.3" @@ -728,25 +750,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "mio" -version = "0.6.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" -dependencies = [ - "cfg-if 0.1.10", - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", - "libc", - "log 0.4.11", - "miow 0.2.2", - "net2", - "slab", - "winapi 0.2.8", -] - [[package]] name = "mio" version = "0.7.7" @@ -755,44 +758,9 @@ checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7" dependencies = [ "libc", "log 0.4.11", - "miow 0.3.6", + "miow", "ntapi", - "winapi 0.3.9", -] - -[[package]] -name = "mio-named-pipes" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656" -dependencies = [ - "log 0.4.11", - "mio 0.6.23", - "miow 0.3.6", - "winapi 0.3.9", -] - -[[package]] -name = "mio-uds" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" -dependencies = [ - "iovec", - "libc", - "mio 0.6.23", -] - -[[package]] -name = "miow" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" -dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", + "winapi", ] [[package]] @@ -802,7 +770,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" dependencies = [ "socket2", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -823,24 +791,13 @@ dependencies = [ "tempfile", ] -[[package]] -name = "net2" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "winapi 0.3.9", -] - [[package]] name = "ntapi" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -929,6 +886,31 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -975,12 +957,6 @@ dependencies = [ "syn", ] -[[package]] -name = "pin-project-lite" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" - [[package]] name = "pin-project-lite" version = "0.2.0" @@ -1053,11 +1029,23 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom", + "getrandom 0.1.15", "libc", - "rand_chacha", - "rand_core", - "rand_hc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", +] + +[[package]] +name = "rand" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18519b42a40024d661e1714153e9ad0c3de27cd495760ceb09710920f1098b1e" +dependencies = [ + "libc", + "rand_chacha 0.3.0", + "rand_core 0.6.1", + "rand_hc 0.3.0", ] [[package]] @@ -1067,7 +1055,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.1", ] [[package]] @@ -1076,7 +1074,16 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom", + "getrandom 0.1.15", +] + +[[package]] +name = "rand_core" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c026d7df8b298d90ccbbc5190bd04d85e159eaf5576caeacf8741da93ccbd2e5" +dependencies = [ + "getrandom 0.2.2", ] [[package]] @@ -1085,7 +1092,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core 0.6.1", ] [[package]] @@ -1118,17 +1134,17 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] name = "reqwest" -version = "0.10.10" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0718f81a8e14c4dbb3b34cf23dc6aaf9ab8a0dfec160c534b3dbca1aaa21f47c" +checksum = "fd281b1030aa675fb90aa994d07187645bb3c8fc756ca766e7c3070b439de9de" dependencies = [ "base64 0.13.0", - "bytes 0.5.6", + "bytes 1.0.1", "encoding_rs", "futures-core", "futures-util", @@ -1141,14 +1157,13 @@ dependencies = [ "lazy_static", "log 0.4.11", "mime", - "mime_guess", "native-tls", "percent-encoding", - "pin-project-lite 0.2.0", + "pin-project-lite", "serde", "serde_urlencoded", - "tokio 0.2.24", - "tokio-tls", + "tokio 1.1.0", + "tokio-native-tls", "url", "wasm-bindgen", "wasm-bindgen-futures", @@ -1168,7 +1183,7 @@ dependencies = [ "spin", "untrusted", "web-sys", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1192,7 +1207,7 @@ dependencies = [ "log 0.4.11", "merge", "regex", - "tokio 0.2.24", + "tokio 1.1.0", "tokio-tungstenite", ] @@ -1209,9 +1224,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" dependencies = [ "lazy_static", - "winapi 0.3.9", + "winapi", ] +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "security-framework" version = "2.0.0" @@ -1315,6 +1336,12 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + [[package]] name = "socket2" version = "0.3.19" @@ -1323,7 +1350,7 @@ checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" dependencies = [ "cfg-if 1.0.0", "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1351,10 +1378,10 @@ checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" dependencies = [ "cfg-if 0.1.10", "libc", - "rand", + "rand 0.7.3", "redox_syscall", "remove_dir_all", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1374,7 +1401,7 @@ checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1392,30 +1419,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" -[[package]] -name = "tokio" -version = "0.2.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099837d3464c16a808060bb3f02263b412f6fafcb5d01c533d309985fbeebe48" -dependencies = [ - "bytes 0.5.6", - "fnv", - "futures-core", - "iovec", - "lazy_static", - "libc", - "memchr", - "mio 0.6.23", - "mio-named-pipes", - "mio-uds", - "num_cpus", - "pin-project-lite 0.1.11", - "signal-hook-registry", - "slab", - "tokio-macros", - "winapi 0.3.9", -] - [[package]] name = "tokio" version = "0.3.6" @@ -1426,15 +1429,35 @@ dependencies = [ "bytes 0.6.0", "libc", "memchr", - "mio 0.7.7", - "pin-project-lite 0.2.0", + "mio", + "pin-project-lite", +] + +[[package]] +name = "tokio" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8efab2086f17abcddb8f756117665c958feee6b2e39974c2f1600592ab3a4195" +dependencies = [ + "autocfg", + "bytes 1.0.1", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "tokio-macros", + "winapi", ] [[package]] name = "tokio-macros" -version = "0.2.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" +checksum = "42517d2975ca3114b22a16192634e8241dc5cc1f130be194645970cc1c371494" dependencies = [ "proc-macro2", "quote", @@ -1442,13 +1465,24 @@ dependencies = [ ] [[package]] -name = "tokio-tls" -version = "0.3.1" +name = "tokio-native-tls" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" dependencies = [ "native-tls", - "tokio 0.2.24", + "tokio 1.1.0", +] + +[[package]] +name = "tokio-stream" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76066865172052eb8796c686f0b441a93df8b08d40a950b062ffb9a426f00edd" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio 1.1.0", ] [[package]] @@ -1466,16 +1500,18 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.3.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +checksum = "feb971a26599ffd28066d387f109746df178eff14d5ea1e235015c5601967a4b" dependencies = [ - "bytes 0.5.6", + "async-stream", + "bytes 1.0.1", "futures-core", "futures-sink", "log 0.4.11", - "pin-project-lite 0.1.11", - "tokio 0.2.24", + "pin-project-lite", + "tokio 1.1.0", + "tokio-stream", ] [[package]] @@ -1491,8 +1527,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3" dependencies = [ "cfg-if 1.0.0", - "log 0.4.11", - "pin-project-lite 0.2.0", + "pin-project-lite", "tracing-core", ] @@ -1535,7 +1570,7 @@ dependencies = [ "input_buffer 0.2.0", "log 0.4.11", "native-tls", - "rand", + "rand 0.7.3", "sha-1 0.8.2", "url", "utf-8", @@ -1554,7 +1589,7 @@ dependencies = [ "httparse", "input_buffer 0.3.1", "log 0.4.11", - "rand", + "rand 0.7.3", "sha-1 0.9.2", "url", "utf-8", @@ -1566,15 +1601,6 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-bidi" version = "0.3.4" @@ -1735,12 +1761,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - [[package]] name = "winapi" version = "0.3.9" @@ -1751,12 +1771,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -1775,15 +1789,5 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" dependencies = [ - "winapi 0.3.9", -] - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -dependencies = [ - "winapi 0.2.8", - "winapi-build", + "winapi", ] diff --git a/rustybot/Cargo.toml b/rustybot/Cargo.toml index b3bc398..7a60bee 100644 --- a/rustybot/Cargo.toml +++ b/rustybot/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" [dependencies] bitfinex = { path= "/home/giulio/dev/bitfinex-rs" } -tokio = { version = "0.2", features=["full"]} +tokio = { version = "1", features=["full"]} tokio-tungstenite = "*" futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] } async-trait = "0.1" diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index cc57e42..f9742dc 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -1,7 +1,7 @@ use core::time::Duration; use log::{error, info}; -use tokio::time::delay_for; +use tokio::time::sleep; use crate::connectors::ExchangeDetails; use crate::currency::{Symbol, SymbolPair}; @@ -60,7 +60,7 @@ impl BfxBot { } async fn update(&mut self) -> Result<(), BoxError> { - delay_for(self.ticker.duration()).await; + sleep(self.ticker.duration()).await; self.ticker.inc(); self.update_exchanges().await?; diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 7cf202a..3a224da 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -4,7 +4,6 @@ use std::ops::Neg; use futures_util::stream::FuturesUnordered; use log::{debug, error, info, trace}; use merge::Merge; -use tokio::stream::StreamExt; use tokio::sync::mpsc::channel; use tokio::sync::mpsc::{Receiver, Sender}; use tokio::sync::oneshot; @@ -17,6 +16,7 @@ use crate::models::{ }; use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop}; use crate::BoxError; +use futures_util::StreamExt; pub type OptionUpdate = (Option>, Option>); diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index b694723..18b9911 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -72,7 +72,7 @@ impl TrailingStop { const BREAK_EVEN_PERC: f64 = 0.1; const MIN_PROFIT_PERC: f64 = TrailingStop::BREAK_EVEN_PERC + 0.2; const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 2.5; - const MAX_LOSS_PERC: f64 = -2.25; + const MAX_LOSS_PERC: f64 = -1.0; pub fn new() -> Self { TrailingStop { -- 2.47.2 From f66d7ef1426b5e5222decec6f4bdeb3b288a6273 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Mon, 25 Jan 2021 16:59:37 +0000 Subject: [PATCH 092/127] no concurrent requests, nonce issue has to be fixed --- rustybot/src/managers.rs | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 3a224da..617f6fd 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::ops::Neg; use futures_util::stream::FuturesUnordered; +use futures_util::StreamExt; use log::{debug, error, info, trace}; use merge::Merge; use tokio::sync::mpsc::channel; @@ -16,7 +17,6 @@ use crate::models::{ }; use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop}; use crate::BoxError; -use futures_util::StreamExt; pub type OptionUpdate = (Option>, Option>); @@ -44,7 +44,7 @@ impl PriceManagerHandle { } pub fn new(pair: SymbolPair, client: Client) -> Self { - let (sender, receiver) = channel(8); + let (sender, receiver) = channel(1); let price_manager = PriceManager::new(receiver, pair, client); tokio::spawn(PriceManagerHandle::run_price_manager(price_manager)); @@ -164,7 +164,7 @@ impl PositionManagerHandle { } pub fn new(pair: SymbolPair, client: Client, strategy: Box) -> Self { - let (sender, receiver) = channel(8); + let (sender, receiver) = channel(1); let manager = PositionManager::new(receiver, pair, client, strategy); @@ -184,7 +184,7 @@ impl PositionManagerHandle { .await?; let response = recv.await?; - + println!("Got response: {:?}", response); Ok(response) } } @@ -228,6 +228,8 @@ impl PositionManager { _ => (None, None), }; + println!("Responding with {:?}", messages); + Ok(msg .respond_to .send((events, messages)) @@ -281,6 +283,7 @@ impl PositionManager { .insert(self.current_tick(), pos_post_tick.clone()); self.active_position = Some(pos_post_tick); + println!("Returning: {:?}", messages); return Ok((events, messages)); } } @@ -326,7 +329,7 @@ impl OrderManagerHandle { } pub fn new(pair: SymbolPair, client: Client, strategy: Box) -> Self { - let (sender, receiver) = channel(8); + let (sender, receiver) = channel(1); let manager = OrderManager::new(receiver, pair, client, strategy); @@ -405,11 +408,15 @@ impl OrderManager { info!("Closing position #{}", position_id); debug!("Retrieving open orders, positions and current prices..."); - let (open_orders, order_book, open_positions) = tokio::join!( - self.client.active_orders(&self.pair), - self.client.order_book(&self.pair), - self.client.active_positions(&self.pair) - ); + // let (open_orders, order_book, open_positions) = tokio::join!( + // self.client.active_orders(&self.pair), + // self.client.order_book(&self.pair), + // self.client.active_positions(&self.pair) + // ); + + let open_orders = self.client.active_orders(&self.pair).await; + let order_book = self.client.order_book(&self.pair).await; + let open_positions = self.client.active_positions(&self.pair).await; let open_orders = match open_orders { Ok(open_orders) => open_orders, @@ -599,6 +606,7 @@ impl PairManager { messages.merge(opt_pos_messages); messages.merge(opt_order_messages); + println!("Messages: {:?}", messages); // TODO: to move into Handler? if let Some(messages) = messages { for m in messages { -- 2.47.2 From 307bbb1b0c5ad7aca4c662741011053208579f51 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Tue, 26 Jan 2021 11:15:04 +0000 Subject: [PATCH 093/127] retry signed requests --- rustybot/Cargo.lock | 103 ++++++++++++++++++++++++++++++++----- rustybot/Cargo.toml | 3 +- rustybot/src/connectors.rs | 92 +++++++++++++++++++++++++++------ rustybot/src/main.rs | 30 +++++++---- rustybot/src/managers.rs | 35 +++++-------- 5 files changed, 202 insertions(+), 61 deletions(-) diff --git a/rustybot/Cargo.lock b/rustybot/Cargo.lock index c3f0e6a..2d62993 100644 --- a/rustybot/Cargo.lock +++ b/rustybot/Cargo.lock @@ -371,46 +371,108 @@ dependencies = [ ] [[package]] -name = "futures-channel" -version = "0.3.8" +name = "futures" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64" +checksum = "da9052a1a50244d8d5aa9bf55cbc2fb6f357c86cc52e46c62ed390a7180cf150" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2d31b7ec7efab6eefc7c57233bb10b847986139d88cc2f5a02a1ae6871a1846" dependencies = [ "futures-core", + "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.8" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" +checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65" + +[[package]] +name = "futures-executor" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e59fdc009a4b3096bf94f740a0f2424c082521f20a9b08c5c07c48d90fd9b9" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28be053525281ad8259d47e4de5de657b25e7bac113458555bb4b70bc6870500" + +[[package]] +name = "futures-macro" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c287d25add322d9f9abdcdc5927ca398917996600182178774032e9f8258fedd" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-retry" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde5a672a61f96552aa5ed9fd9c81c3fbdae4be9b1e205d6eaf17c83705adc0f" +dependencies = [ + "futures", + "pin-project-lite", + "tokio 1.1.0", +] [[package]] name = "futures-sink" -version = "0.3.8" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d" +checksum = "caf5c69029bda2e743fddd0582d1083951d65cc9539aebf8812f36c3491342d6" [[package]] name = "futures-task" -version = "0.3.8" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d" +checksum = "13de07eb8ea81ae445aca7b69f5f7bf15d7bf4912d8ca37d6645c77ae8a58d86" dependencies = [ "once_cell", ] [[package]] name = "futures-util" -version = "0.3.8" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2" +checksum = "632a8cd0f2a4b3fdea1657f08bde063848c3bd00f9bbf6e256b8be78802e624b" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", "futures-sink", "futures-task", - "pin-project 1.0.2", + "memchr", + "pin-project-lite", "pin-utils", + "proc-macro-hack", + "proc-macro-nested", "slab", ] @@ -959,9 +1021,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.0" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c" +checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827" [[package]] name = "pin-utils" @@ -1005,6 +1067,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro-nested" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" + [[package]] name = "proc-macro2" version = "1.0.24" @@ -1203,6 +1277,7 @@ dependencies = [ "dyn-clone", "fern", "float-cmp", + "futures-retry", "futures-util", "log 0.4.11", "merge", diff --git a/rustybot/Cargo.toml b/rustybot/Cargo.toml index 7a60bee..b22004a 100644 --- a/rustybot/Cargo.toml +++ b/rustybot/Cargo.toml @@ -19,4 +19,5 @@ fern = {version = "0.6", features = ["colored"]} chrono = "0.4" byteorder = "1" float-cmp = "0.8" -merge = "0.1" \ No newline at end of file +merge = "0.1" +futures-retry = "0.6" \ No newline at end of file diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 2a74e83..626a995 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -7,6 +7,10 @@ use async_trait::async_trait; use bitfinex::api::Bitfinex; use bitfinex::orders::{CancelOrderForm, OrderMeta, OrderResponse}; use bitfinex::ticker::TradingPairTicker; +use futures_retry::{FutureRetry, RetryPolicy, StreamRetryExt}; +use futures_util::task::FutureObj; +use log::trace; +use tokio::time::Duration; use crate::currency::SymbolPair; use crate::models::{ @@ -14,6 +18,8 @@ use crate::models::{ PriceTicker, TradingPlatform, }; use crate::BoxError; +use std::error::Error; +use tokio::macros::support::Future; #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub enum Exchange { @@ -142,6 +148,13 @@ pub struct BitfinexConnector { impl BitfinexConnector { const AFFILIATE_CODE: &'static str = "XPebOgHxA"; + fn handle_small_nonce_error(e: BoxError) -> RetryPolicy { + if e.to_string().contains("nonce: small") { + return RetryPolicy::WaitRetry(Duration::from_millis(1)); + } + return RetryPolicy::ForwardError(e); + } + pub fn new(api_key: &str, api_secret: &str) -> Self { BitfinexConnector { bfx: Bitfinex::new(Some(api_key.into()), Some(api_secret.into())), @@ -164,7 +177,12 @@ impl Connector for BitfinexConnector { } async fn active_positions(&self, pair: &SymbolPair) -> Result>, BoxError> { - let active_positions = self.bfx.positions.active_positions().await?; + let (active_positions, _) = FutureRetry::new( + move || self.bfx.positions.active_positions(), + BitfinexConnector::handle_small_nonce_error, + ) + .await + .map_err(|(e, attempts)| e)?; let positions: Vec<_> = active_positions .into_iter() @@ -172,11 +190,8 @@ impl Connector for BitfinexConnector { .filter(|x: &Position| x.pair() == pair) .collect(); - if positions.is_empty() { - Ok(None) - } else { - Ok(Some(positions)) - } + trace!("\t[PositionManager] Retrieved positions for {}", pair); + Ok((!positions.is_empty()).then_some(positions)) } async fn current_prices(&self, pair: &SymbolPair) -> Result { @@ -188,7 +203,12 @@ impl Connector for BitfinexConnector { } async fn active_orders(&self, _: &SymbolPair) -> Result, BoxError> { - let response = self.bfx.orders.active_orders().await?; + let (response, _) = FutureRetry::new( + move || self.bfx.orders.active_orders(), + BitfinexConnector::handle_small_nonce_error, + ) + .await + .map_err(|(e, attempts)| e)?; Ok(response.iter().map(Into::into).collect()) } @@ -227,7 +247,22 @@ impl Connector for BitfinexConnector { BitfinexConnector::AFFILIATE_CODE.to_string(), )); - let response = self.bfx.orders.submit_order(&order_form).await?; + // retry to submit the order until it succeeds. + // the function may fail due to concurrent signed requests + // parsed in different times by the server + let response = { + loop { + match self.bfx.orders.submit_order(&order_form).await { + Ok(response) => break response, + Err(e) => { + if !e.to_string().contains("nonce: small") { + return Err(e); + } + tokio::time::sleep(Duration::from_nanos(1)).await; + } + } + } + }; Ok((&response).try_into()?) } @@ -235,13 +270,26 @@ impl Connector for BitfinexConnector { async fn order_book(&self, pair: &SymbolPair) -> Result { let symbol_name = BitfinexConnector::format_trading_pair(pair); - let x = self - .bfx - .book - .trading_pair(symbol_name, bitfinex::book::BookPrecision::P0) - .await?; + let response = { + loop { + match self + .bfx + .book + .trading_pair(symbol_name.clone(), bitfinex::book::BookPrecision::P0) + .await + { + Ok(response) => break response, + Err(e) => { + if !e.to_string().contains("nonce: small") { + return Err(e); + } + tokio::time::sleep(Duration::from_nanos(1)).await; + } + } + } + }; - let entries = x + let entries = response .into_iter() .map(|x| OrderBookEntry::Trading { price: x.price, @@ -256,7 +304,21 @@ impl Connector for BitfinexConnector { async fn cancel_order(&self, order: &ActiveOrder) -> Result { let cancel_form = order.into(); - Ok((&self.bfx.orders.cancel_order(&cancel_form).await?).try_into()?) + let response = { + loop { + match self.bfx.orders.cancel_order(&cancel_form).await { + Ok(response) => break response, + Err(e) => { + if !e.to_string().contains("nonce: small") { + return Err(e); + } + tokio::time::sleep(Duration::from_nanos(1)).await; + } + } + } + }; + + Ok((&response).try_into()?) } } diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index 1dde9fc..8a0d3e5 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -2,13 +2,12 @@ #![feature(bool_to_option)] use fern::colors::{Color, ColoredLevelConfig}; -use log::LevelFilter::Debug; +use log::LevelFilter::{Debug, Trace}; use tokio::time::Duration; use crate::bot::BfxBot; -use crate::connectors::ExchangeKind; +use crate::connectors::ExchangeDetails; use crate::currency::Symbol; -use crate::strategy::TrailingStop; mod bot; mod connectors; @@ -25,18 +24,31 @@ pub type BoxError = Box; async fn main() -> Result<(), BoxError> { setup_logger()?; + // TEST let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2"; let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli"; - let bitfinex = ExchangeKind::Bitfinex { - api_key: test_api_key.into(), - api_secret: test_api_secret.into(), + // REAL + // let orders_api_key = "hc5nDvYbFYJZMKdnzYq8P4AzCSwjxfQHnMyrg69Sf4c"; + // let orders_api_secret = "53x9goIOpbOtBoPi7dmigK5Cq5e0282EUO2qRIMEXlh"; + // let prices_api_key = "gTfFZUCwRBE0Z9FZjyk9HNe4lZ7XuiZY9rrW71SyUr9"; + // let prices_api_secret = "zWbxvoFZad3BPIiXK4DKfEvC0YsAuaApbeAyI8OBXgN"; + // let positions_api_key = "PfR7BadPZPNdVZnkHFBfAjsg7gjt8pAecMj5B8eRPFi"; + // let positions_api_secret = "izzvxtE3XsBBRpVCHGJ8f60UA56SmPNbBvJGVd67aqD"; + + let bitfinex = ExchangeDetails::Bitfinex { + prices_api_key: test_api_key.into(), + prices_api_secret: test_api_secret.into(), + orders_api_key: test_api_key.into(), + orders_api_secret: test_api_secret.into(), + positions_api_key: test_api_key.into(), + positions_api_secret: test_api_secret.into(), }; let mut bot = BfxBot::new( vec![bitfinex], - vec![Symbol::TESTBTC], - Symbol::TESTUSD, + vec![Symbol::BTC, Symbol::ETH, Symbol::XMR], + Symbol::USD, Duration::new(1, 0), ); @@ -60,7 +72,7 @@ fn setup_logger() -> Result<(), fern::InitError> { message )) }) - .level(Debug) + .level(Trace) .filter(|metadata| metadata.target().contains("rustybot")) .chain(std::io::stdout()) // .chain(fern::log_file("rustico.log")?) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 617f6fd..b0e02c8 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -184,7 +184,6 @@ impl PositionManagerHandle { .await?; let response = recv.await?; - println!("Got response: {:?}", response); Ok(response) } } @@ -228,8 +227,6 @@ impl PositionManager { _ => (None, None), }; - println!("Responding with {:?}", messages); - Ok(msg .respond_to .send((events, messages)) @@ -237,7 +234,7 @@ impl PositionManager { } pub async fn update(&mut self, tick: u64) -> Result { - // debug!("Updating {}", self.pair); + trace!("\t[PositionManager] Updating {}", self.pair); let opt_active_positions = self.client.active_positions(&self.pair).await?; self.current_tick = tick; @@ -283,7 +280,6 @@ impl PositionManager { .insert(self.current_tick(), pos_post_tick.clone()); self.active_position = Some(pos_post_tick); - println!("Returning: {:?}", messages); return Ok((events, messages)); } } @@ -408,15 +404,11 @@ impl OrderManager { info!("Closing position #{}", position_id); debug!("Retrieving open orders, positions and current prices..."); - // let (open_orders, order_book, open_positions) = tokio::join!( - // self.client.active_orders(&self.pair), - // self.client.order_book(&self.pair), - // self.client.active_positions(&self.pair) - // ); - - let open_orders = self.client.active_orders(&self.pair).await; - let order_book = self.client.order_book(&self.pair).await; - let open_positions = self.client.active_positions(&self.pair).await; + let (open_orders, order_book, open_positions) = tokio::join!( + self.client.active_orders(&self.pair), + self.client.order_book(&self.pair), + self.client.active_positions(&self.pair) + ); let open_orders = match open_orders { Ok(open_orders) => open_orders, @@ -518,13 +510,13 @@ impl OrderManager { // TODO: finish me pub async fn update(&self) -> Result { - trace!("\tUpdating {} order manager.", self.pair); - // - // let (open_orders, opt_open_positions) = tokio::join!( - // self.client.active_orders(&self.pair), - // self.client.active_positions(&self.pair) - // ); - // let (_open_orders, _opt_open_positions) = (open_orders?, opt_open_positions?); + trace!("\t[OrderManager] Updating {}", self.pair); + + let (open_orders, opt_open_positions) = tokio::join!( + self.client.active_orders(&self.pair), + self.client.active_positions(&self.pair) + ); + let (_open_orders, _opt_open_positions) = (open_orders?, opt_open_positions?); Ok((None, None)) } @@ -606,7 +598,6 @@ impl PairManager { messages.merge(opt_pos_messages); messages.merge(opt_order_messages); - println!("Messages: {:?}", messages); // TODO: to move into Handler? if let Some(messages) = messages { for m in messages { -- 2.47.2 From 99904683a103c3d14aa9ca4c136d8530e7de9fa7 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Tue, 26 Jan 2021 16:18:50 +0000 Subject: [PATCH 094/127] min profit 0.4 --- rustybot/src/strategy.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 18b9911..34803e1 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -70,7 +70,7 @@ pub struct TrailingStop { impl TrailingStop { const BREAK_EVEN_PERC: f64 = 0.1; - const MIN_PROFIT_PERC: f64 = TrailingStop::BREAK_EVEN_PERC + 0.2; + const MIN_PROFIT_PERC: f64 = 0.4; const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 2.5; const MAX_LOSS_PERC: f64 = -1.0; -- 2.47.2 From 15c1d5b84ad4c7c0acfe50b8cbed09c1229987e4 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Tue, 26 Jan 2021 16:19:00 +0000 Subject: [PATCH 095/127] added polka dot --- rustybot/src/currency.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rustybot/src/currency.rs b/rustybot/src/currency.rs index 0798c87..23ab15a 100644 --- a/rustybot/src/currency.rs +++ b/rustybot/src/currency.rs @@ -26,8 +26,13 @@ impl Symbol { pub const BTC: Symbol = Symbol::new_static("BTC"); pub const ETH: Symbol = Symbol::new_static("ETH"); pub const LTC: Symbol = Symbol::new_static("LTC"); + pub const DOT: Symbol = Symbol::new_static("DOT"); + + // Paper trading pub const TESTBTC: Symbol = Symbol::new_static("TESTBTC"); pub const TESTUSD: Symbol = Symbol::new_static("TESTUSD"); + + // Fiat coins pub const USD: Symbol = Symbol::new_static("USD"); pub const GBP: Symbol = Symbol::new_static("GBP"); pub const EUR: Symbol = Symbol::new_static("EUR"); -- 2.47.2 From ef618ad7542d4ea62b563e3db5b1779e76dfc71c Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Tue, 26 Jan 2021 16:19:10 +0000 Subject: [PATCH 096/127] removed extra api keys --- rustybot/src/connectors.rs | 45 ++++++++++---------------------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 626a995..3e27ff1 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -28,14 +28,7 @@ pub enum Exchange { #[derive(Eq, PartialEq, Hash, Clone, Debug)] pub enum ExchangeDetails { - Bitfinex { - prices_api_key: String, - prices_api_secret: String, - orders_api_key: String, - orders_api_secret: String, - positions_api_key: String, - positions_api_secret: String, - }, + Bitfinex { api_key: String, api_secret: String }, } /// You do **not** have to wrap the `Client` in an [`Rc`] or [`Arc`] to **reuse** it, @@ -43,32 +36,18 @@ pub enum ExchangeDetails { #[derive(Clone, Debug)] pub struct Client { exchange: Exchange, - prices_connector: Arc>, - orders_connector: Arc>, - positions_connector: Arc>, + inner: Arc>, } impl Client { pub fn new(exchange: &ExchangeDetails) -> Self { match exchange { ExchangeDetails::Bitfinex { - prices_api_key, - prices_api_secret, - orders_api_key, - orders_api_secret, - positions_api_key, - positions_api_secret, + api_key, + api_secret, } => Self { exchange: Exchange::Bitfinex, - prices_connector: Arc::new(Box::new( - (BitfinexConnector::new(prices_api_key, prices_api_secret)), - )), - orders_connector: Arc::new(Box::new( - (BitfinexConnector::new(orders_api_key, orders_api_secret)), - )), - positions_connector: Arc::new(Box::new( - (BitfinexConnector::new(positions_api_key, positions_api_secret)), - )), + inner: Arc::new(Box::new((BitfinexConnector::new(api_key, api_secret)))), }, } } @@ -79,8 +58,8 @@ impl Client { ) -> Result>, BoxError> { // retrieving open positions and order book to calculate effective profit/loss let (positions, order_book) = tokio::join!( - self.positions_connector.active_positions(pair), - self.orders_connector.order_book(pair) + self.inner.active_positions(pair), + self.inner.order_book(pair) ); let (mut positions, order_book) = (positions?, order_book?); @@ -100,23 +79,23 @@ impl Client { } pub async fn current_prices(&self, pair: &SymbolPair) -> Result { - self.prices_connector.current_prices(pair).await + self.inner.current_prices(pair).await } pub async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError> { - self.orders_connector.active_orders(pair).await + self.inner.active_orders(pair).await } pub async fn submit_order(&self, order: &OrderForm) -> Result { - self.orders_connector.submit_order(order).await + self.inner.submit_order(order).await } pub async fn order_book(&self, pair: &SymbolPair) -> Result { - self.orders_connector.order_book(pair).await + self.inner.order_book(pair).await } pub async fn cancel_order(&self, order: &ActiveOrder) -> Result { - self.orders_connector.cancel_order(order).await + self.inner.cancel_order(order).await } } -- 2.47.2 From ac1fd3669ff8ed9219f4f06a6a741ba700c16595 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Tue, 26 Jan 2021 17:01:58 +0000 Subject: [PATCH 097/127] removed extra function for orderstrategy (for now). order manager update is now working --- rustybot/src/managers.rs | 151 +++++++++++++++++---------------------- rustybot/src/strategy.rs | 85 +++++++++++++++------- 2 files changed, 125 insertions(+), 111 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index b0e02c8..cba7211 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -404,104 +404,52 @@ impl OrderManager { info!("Closing position #{}", position_id); debug!("Retrieving open orders, positions and current prices..."); - let (open_orders, order_book, open_positions) = tokio::join!( + let (res_open_orders, res_order_book, res_open_positions) = tokio::join!( self.client.active_orders(&self.pair), self.client.order_book(&self.pair), self.client.active_positions(&self.pair) ); - let open_orders = match open_orders { - Ok(open_orders) => open_orders, - Err(e) => { - error!("Could not retrieve open orders: {}", e); - return Err(e); - } - }; + let (open_orders, order_book, open_positions) = + (res_open_orders?, res_order_book?, res_open_positions?); - let order_book = match order_book { - Ok(order_book) => order_book, - Err(e) => { - error!("Could not retrieve order book: {}", e); - return Err(e); - } - }; - - let position = match open_positions { - Ok(opt_positions) => { - let positions = opt_positions.ok_or::("No open positions".into())?; - - positions - .into_iter() - .find(|x| x.id() == position_id) - .ok_or::("Position #{} not found in open positions".into())? - } - - Err(e) => { - error!("Could not retrieve open positions: {}", e); - return Err(e); - } - }; + let position = open_positions + .ok_or("No open positions!")? + .into_iter() + .find(|x| x.id() == position_id) + .ok_or("Position #{} not found in open positions.")?; let opt_position_order = open_orders .iter() .find(|x| x.current_form.amount().neg() == position.amount()); // checking if the position has an open order. - // If so, the strategy method is called, otherwise we open - // an undercut limit order at the best current price. - match opt_position_order { + // If so, don't do anything since the order is taken care of + // in the update phase. + // If no order is open, send an undercut limit order at the best current price. + if let None = opt_position_order { // No open order, undercutting best price with limit order - None => { - let closing_price = self.best_closing_price(&position, &order_book); + let closing_price = self.best_closing_price(&position, &order_book); - // TODO: hardocoded platform to Margin! - let order_form = OrderForm::new( - self.pair.clone(), - OrderKind::Limit { - price: closing_price, - amount: position.amount().neg(), - }, - TradingPlatform::Margin, + // TODO: hardcoded platform to Margin! + let order_form = OrderForm::new( + self.pair.clone(), + OrderKind::Limit { + price: closing_price, + amount: position.amount().neg(), + }, + TradingPlatform::Margin, + ); + + info!("Submitting {} order", order_form.kind()); + if let Err(e) = self.client.submit_order(&order_form).await { + error!( + "Could not submit {} to close position #{}: {}", + order_form.kind(), + position.id(), + e ); - - info!("Submitting {} order", order_form.kind()); - if let Err(e) = self.client.submit_order(&order_form).await { - error!( - "Could not submit {} to close position #{}: {}", - order_form.kind(), - position.id(), - e - ); - return Err(e); - } - } - Some(active_order) => { - debug!( - "Found open order, calling \"{}\" strategy.", - self.strategy.name() - ); - - let (_, strat_messages) = - self.strategy - .on_position_order(active_order, &position, &order_book)?; - - if let Some(messages) = strat_messages { - for m in messages { - match m { - Message::SubmitOrder { order } => { - info!("Closing open order."); - info!("Cancelling open order #{}", active_order.id); - self.client.cancel_order(active_order).await?; - - info!("Submitting {}...", order.kind()); - self.client.submit_order(&order).await?; - } - _ => { - debug!("Received unsupported message from order strategy. Unimplemented.") - } - } - } - } + return Err(e); } } @@ -512,11 +460,42 @@ impl OrderManager { pub async fn update(&self) -> Result { trace!("\t[OrderManager] Updating {}", self.pair); - let (open_orders, opt_open_positions) = tokio::join!( + let (res_open_orders, res_order_book) = tokio::join!( self.client.active_orders(&self.pair), - self.client.active_positions(&self.pair) + self.client.order_book(&self.pair) ); - let (_open_orders, _opt_open_positions) = (open_orders?, opt_open_positions?); + + let (open_orders, order_book) = (res_open_orders?, res_order_book?); + + for active_order in open_orders { + debug!( + "Found open order, calling \"{}\" strategy.", + self.strategy.name() + ); + + let (_, strat_messages) = self.strategy.on_open_order(&active_order, &order_book)?; + + if let Some(messages) = strat_messages { + for m in messages { + match m { + Message::SubmitOrder { order: order_form } => { + info!("Closing open order..."); + info!("\tCancelling open order #{}", &active_order.id); + self.client.cancel_order(&active_order).await?; + + info!("\tSubmitting {}...", order_form.kind()); + self.client.submit_order(&order_form).await?; + info!("Done!"); + } + _ => { + debug!( + "Received unsupported message from order strategy. Unimplemented." + ) + } + } + } + } + } Ok((None, None)) } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 34803e1..3ac2f03 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -5,6 +5,7 @@ use dyn_clone::DynClone; use log::info; use crate::events::{Event, EventKind, EventMetadata, Message}; +use crate::managers::OptionUpdate; use crate::models::{ ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionProfitState, PositionState, TradingPlatform, @@ -42,15 +43,19 @@ pub trait OrderStrategy: DynClone + Send + Sync { fn name(&self) -> String; /// This method is called when the OrderManager checks the open orders on a new tick. /// It should manage if some orders have to be closed or keep open. - fn on_open_order(&self); - /// This method is called when the OrderManager is requested to close - /// a position that has an open order associated to it. - fn on_position_order( + fn on_open_order( &self, order: &ActiveOrder, - open_position: &Position, order_book: &OrderBook, - ) -> Result<(Option>, Option>), BoxError>; + ) -> Result; + // /// This method is called when the OrderManager is requested to close + // /// a position that has an open order associated to it. + // fn on_position_order( + // &self, + // order: &ActiveOrder, + // open_position: &Position, + // order_book: &OrderBook, + // ) -> Result; } impl Debug for dyn OrderStrategy { @@ -81,12 +86,6 @@ impl TrailingStop { } fn update_stop_percentage(&mut self, position: &Position) { - info!( - "\tState: {:?} | PL%: {:0.2}", - position.profit_state().unwrap(), - position.pl_perc() - ); - if let Some(profit_state) = position.profit_state() { let profit_state_delta = match profit_state { PositionProfitState::MinimumProfit => Some(0.2), @@ -95,12 +94,6 @@ impl TrailingStop { }; if let Some(profit_state_delta) = profit_state_delta { - println!( - "PL%: {:0.2} | Delta: {}", - position.pl_perc(), - profit_state_delta - ); - let current_stop_percentage = position.pl_perc() - profit_state_delta; match profit_state { @@ -121,6 +114,12 @@ impl TrailingStop { _ => {} } } + info!( + "\tState: {:?} | PL%: {:0.2} | Stop: {:0.2}", + position.profit_state().unwrap(), + position.pl_perc(), + self.stop_percentages.get(&position.id()).unwrap_or(&0.0) + ); } } } @@ -223,6 +222,7 @@ impl PositionStrategy for TrailingStop { if let Some(profit_state) = position.profit_state() { match profit_state { PositionProfitState::Critical => { + info!("Maximum loss reached. Closing position."); return (position, None, Some(vec![close_message])); } _ => {} @@ -267,16 +267,11 @@ impl OrderStrategy for FastOrderStrategy { "Fast order strategy".into() } - fn on_open_order(&self) { - unimplemented!() - } - - fn on_position_order( + fn on_open_order( &self, order: &ActiveOrder, - _: &Position, order_book: &OrderBook, - ) -> Result<(Option>, Option>), BoxError> { + ) -> Result { let mut messages = vec![]; // long @@ -310,4 +305,44 @@ impl OrderStrategy for FastOrderStrategy { Ok((None, (!messages.is_empty()).then_some(messages))) } + + // fn on_position_order( + // &self, + // order: &ActiveOrder, + // _: &Position, + // order_book: &OrderBook, + // ) -> Result { + // let mut messages = vec![]; + // + // // long + // let offer_comparison = { + // if order.current_form.amount() > 0.0 { + // order_book.highest_bid() + // } else { + // order_book.lowest_ask() + // } + // }; + // + // // if the best offer is higher than our threshold, + // // ask the manager to close the position with a market order + // let order_price = order + // .current_form + // .price() + // .ok_or("The active order does not have a price!")?; + // let delta = (1.0 - (offer_comparison / order_price)).abs() * 100.0; + // + // if delta > self.threshold { + // messages.push(Message::SubmitOrder { + // order: OrderForm::new( + // order.symbol.clone(), + // OrderKind::Market { + // amount: order.current_form.amount(), + // }, + // order.current_form.platform().clone(), + // ), + // }) + // } + // + // Ok((None, (!messages.is_empty()).then_some(messages))) + // } } -- 2.47.2 From fbdb481aa03aa2a9e534a12408671153898128a8 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Tue, 26 Jan 2021 17:12:39 +0000 Subject: [PATCH 098/127] positionclosed eventkind --- rustybot/src/events.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index a8f029c..1fc24cd 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -44,6 +44,7 @@ pub enum EventKind { TrailingStopMoved, OrderSubmitted, NewTick, + PositionClosed { position_id: u64 }, } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -- 2.47.2 From 7230b7c67deb7a15251e896163cc27b954c34066 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Tue, 26 Jan 2021 17:13:14 +0000 Subject: [PATCH 099/127] filter active orders based on pair --- rustybot/src/connectors.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 3e27ff1..24bd9b4 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -1,4 +1,5 @@ use std::convert::{TryFrom, TryInto}; +use std::error::Error; use std::fmt::{Debug, Formatter}; use std::str::FromStr; use std::sync::Arc; @@ -10,6 +11,7 @@ use bitfinex::ticker::TradingPairTicker; use futures_retry::{FutureRetry, RetryPolicy, StreamRetryExt}; use futures_util::task::FutureObj; use log::trace; +use tokio::macros::support::Future; use tokio::time::Duration; use crate::currency::SymbolPair; @@ -18,8 +20,6 @@ use crate::models::{ PriceTicker, TradingPlatform, }; use crate::BoxError; -use std::error::Error; -use tokio::macros::support::Future; #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub enum Exchange { @@ -83,7 +83,13 @@ impl Client { } pub async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError> { - self.inner.active_orders(pair).await + Ok(self + .inner + .active_orders(pair) + .await? + .into_iter() + .filter(|x| &x.symbol == pair) + .collect()) } pub async fn submit_order(&self, order: &OrderForm) -> Result { -- 2.47.2 From 382f9c81064841e60a0ee5c56573ca5a0d24a5b8 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 27 Jan 2021 17:00:47 +0000 Subject: [PATCH 100/127] order manager on its own --- rustybot/src/managers.rs | 22 +++++++++++++++++++--- rustybot/src/strategy.rs | 6 +++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index cba7211..3ea28d4 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -8,6 +8,7 @@ use merge::Merge; use tokio::sync::mpsc::channel; use tokio::sync::mpsc::{Receiver, Sender}; use tokio::sync::oneshot; +use tokio::time::Duration; use crate::connectors::{Client, ExchangeDetails}; use crate::currency::SymbolPair; @@ -318,9 +319,25 @@ pub struct OrderManagerHandle { } impl OrderManagerHandle { + const SLEEP_DURATION: u64 = 5; + async fn run_order_manager(mut manager: OrderManager) { - while let Some(msg) = manager.receiver.recv().await { - manager.handle_message(msg).await.unwrap(); + let mut sleep = tokio::time::interval(Duration::from_secs(5)); + + loop { + tokio::select! { + opt_msg= manager.receiver.recv() => { + if let Some(msg) = opt_msg { + manager.handle_message(msg).await.unwrap() + + } + + + }, + _ = sleep.tick() => { + manager.update().await.unwrap(); + } + } } } @@ -456,7 +473,6 @@ impl OrderManager { Ok((None, None)) } - // TODO: finish me pub async fn update(&self) -> Result { trace!("\t[OrderManager] Updating {}", self.pair); diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 3ac2f03..8b88d48 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -75,9 +75,9 @@ pub struct TrailingStop { impl TrailingStop { const BREAK_EVEN_PERC: f64 = 0.1; - const MIN_PROFIT_PERC: f64 = 0.4; - const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 2.5; - const MAX_LOSS_PERC: f64 = -1.0; + const MIN_PROFIT_PERC: f64 = 0.7; + const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 1.75; + const MAX_LOSS_PERC: f64 = -1.75; pub fn new() -> Self { TrailingStop { -- 2.47.2 From 6ed510f2ccda04ecb77da454509ac574c5da0d05 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 27 Jan 2021 17:01:26 +0000 Subject: [PATCH 101/127] cargo fix --- rustybot/src/connectors.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 24bd9b4..55644f2 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -1,5 +1,5 @@ use std::convert::{TryFrom, TryInto}; -use std::error::Error; + use std::fmt::{Debug, Formatter}; use std::str::FromStr; use std::sync::Arc; @@ -8,8 +8,8 @@ use async_trait::async_trait; use bitfinex::api::Bitfinex; use bitfinex::orders::{CancelOrderForm, OrderMeta, OrderResponse}; use bitfinex::ticker::TradingPairTicker; -use futures_retry::{FutureRetry, RetryPolicy, StreamRetryExt}; -use futures_util::task::FutureObj; +use futures_retry::{FutureRetry, RetryPolicy}; + use log::trace; use tokio::macros::support::Future; use tokio::time::Duration; @@ -47,7 +47,7 @@ impl Client { api_secret, } => Self { exchange: Exchange::Bitfinex, - inner: Arc::new(Box::new((BitfinexConnector::new(api_key, api_secret)))), + inner: Arc::new(Box::new(BitfinexConnector::new(api_key, api_secret))), }, } } @@ -167,7 +167,7 @@ impl Connector for BitfinexConnector { BitfinexConnector::handle_small_nonce_error, ) .await - .map_err(|(e, attempts)| e)?; + .map_err(|(e, _attempts)| e)?; let positions: Vec<_> = active_positions .into_iter() @@ -193,7 +193,7 @@ impl Connector for BitfinexConnector { BitfinexConnector::handle_small_nonce_error, ) .await - .map_err(|(e, attempts)| e)?; + .map_err(|(e, _attempts)| e)?; Ok(response.iter().map(Into::into).collect()) } -- 2.47.2 From dd3786486c16b936fa9ed18cd04475a4a4302ec4 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 27 Jan 2021 17:04:37 +0000 Subject: [PATCH 102/127] formatting --- rustybot/src/managers.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 3ea28d4..5353059 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -326,17 +326,14 @@ impl OrderManagerHandle { loop { tokio::select! { - opt_msg= manager.receiver.recv() => { - if let Some(msg) = opt_msg { + opt_msg = manager.receiver.recv() => { + if let Some(msg) = opt_msg { manager.handle_message(msg).await.unwrap() - - } - - - }, - _ = sleep.tick() => { - manager.update().await.unwrap(); - } + } + }, + _ = sleep.tick() => { + manager.update().await.unwrap(); + } } } } -- 2.47.2 From d445dc137ae0378c6b9f04ccec7a49338060a9df Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 27 Jan 2021 17:07:44 +0000 Subject: [PATCH 103/127] removed order manager update from pairmanager update phase --- rustybot/src/managers.rs | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 5353059..9da5656 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -322,7 +322,8 @@ impl OrderManagerHandle { const SLEEP_DURATION: u64 = 5; async fn run_order_manager(mut manager: OrderManager) { - let mut sleep = tokio::time::interval(Duration::from_secs(5)); + let mut sleep = + tokio::time::interval(Duration::from_secs(OrderManagerHandle::SLEEP_DURATION)); loop { tokio::select! { @@ -348,19 +349,6 @@ impl OrderManagerHandle { Self { sender } } - pub async fn update(&mut self, tick: u64) -> Result { - let (send, recv) = oneshot::channel(); - - self.sender - .send(ActorMessage { - message: Message::Update { tick }, - respond_to: send, - }) - .await?; - - Ok(recv.await?) - } - pub async fn close_position(&mut self, position_id: u64) -> Result { let (send, recv) = oneshot::channel(); @@ -572,23 +560,19 @@ impl PairManager { let mut events = None; let mut messages = None; - let (price_results, pos_results, order_results) = tokio::join!( + let (price_results, pos_results) = tokio::join!( self.price_manager.update(tick), self.position_manager.update(tick), - self.order_manager.update(tick) ); let (opt_price_events, opt_price_messages) = price_results?; let (opt_pos_events, opt_pos_messages) = pos_results?; - let (opt_order_events, opt_order_messages) = order_results?; events.merge(opt_price_events); events.merge(opt_pos_events); - events.merge(opt_order_events); messages.merge(opt_price_messages); messages.merge(opt_pos_messages); - messages.merge(opt_order_messages); // TODO: to move into Handler? if let Some(messages) = messages { -- 2.47.2 From 7357d48115cb0ea5b53570642f0c4e09255aba44 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 27 Jan 2021 17:12:20 +0000 Subject: [PATCH 104/127] don't crash if no open positions are found --- rustybot/src/managers.rs | 68 ++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 9da5656..bb6e3f8 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -415,43 +415,43 @@ impl OrderManager { let (open_orders, order_book, open_positions) = (res_open_orders?, res_order_book?, res_open_positions?); - let position = open_positions - .ok_or("No open positions!")? - .into_iter() - .find(|x| x.id() == position_id) - .ok_or("Position #{} not found in open positions.")?; + // if there are open positions + if let Some(open_positions) = open_positions { + // if we find an open position with the ID we are looking for + if let Some(position) = open_positions.into_iter().find(|x| x.id() == position_id) { + let opt_position_order = open_orders + .iter() + .find(|x| x.current_form.amount().neg() == position.amount()); - let opt_position_order = open_orders - .iter() - .find(|x| x.current_form.amount().neg() == position.amount()); + // checking if the position has an open order. + // If so, don't do anything since the order is taken care of + // in the update phase. + // If no order is open, send an undercut limit order at the best current price. + if let None = opt_position_order { + // No open order, undercutting best price with limit order + let closing_price = self.best_closing_price(&position, &order_book); - // checking if the position has an open order. - // If so, don't do anything since the order is taken care of - // in the update phase. - // If no order is open, send an undercut limit order at the best current price. - if let None = opt_position_order { - // No open order, undercutting best price with limit order - let closing_price = self.best_closing_price(&position, &order_book); + // TODO: hardcoded platform to Margin! + let order_form = OrderForm::new( + self.pair.clone(), + OrderKind::Limit { + price: closing_price, + amount: position.amount().neg(), + }, + TradingPlatform::Margin, + ); - // TODO: hardcoded platform to Margin! - let order_form = OrderForm::new( - self.pair.clone(), - OrderKind::Limit { - price: closing_price, - amount: position.amount().neg(), - }, - TradingPlatform::Margin, - ); - - info!("Submitting {} order", order_form.kind()); - if let Err(e) = self.client.submit_order(&order_form).await { - error!( - "Could not submit {} to close position #{}: {}", - order_form.kind(), - position.id(), - e - ); - return Err(e); + info!("Submitting {} order", order_form.kind()); + if let Err(e) = self.client.submit_order(&order_form).await { + error!( + "Could not submit {} to close position #{}: {}", + order_form.kind(), + position.id(), + e + ); + return Err(e); + } + } } } -- 2.47.2 From c930dce131c53df8e9cc2055e7c410446726d489 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 27 Jan 2021 20:18:06 +0000 Subject: [PATCH 105/127] support for balance transfer API (bitfinex) --- rustybot/Cargo.lock | 2 +- rustybot/src/connectors.rs | 62 +++++++++++++++++++++++++++++++++----- rustybot/src/managers.rs | 3 +- rustybot/src/models.rs | 6 ++++ 4 files changed, 63 insertions(+), 10 deletions(-) diff --git a/rustybot/Cargo.lock b/rustybot/Cargo.lock index 2d62993..cba87b0 100644 --- a/rustybot/Cargo.lock +++ b/rustybot/Cargo.lock @@ -107,7 +107,7 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bitfinex" -version = "0.4.3" +version = "0.5.0" dependencies = [ "bitflags", "chrono", diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 55644f2..cc60ea7 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -1,25 +1,24 @@ use std::convert::{TryFrom, TryInto}; - use std::fmt::{Debug, Formatter}; use std::str::FromStr; use std::sync::Arc; use async_trait::async_trait; use bitfinex::api::Bitfinex; -use bitfinex::orders::{CancelOrderForm, OrderMeta, OrderResponse}; +use bitfinex::orders::{CancelOrderForm, OrderMeta}; use bitfinex::ticker::TradingPairTicker; use futures_retry::{FutureRetry, RetryPolicy}; - use log::trace; use tokio::macros::support::Future; use tokio::time::Duration; -use crate::currency::SymbolPair; +use crate::currency::{Symbol, SymbolPair}; use crate::models::{ ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionState, - PriceTicker, TradingPlatform, + PriceTicker, TradingPlatform, WalletKind, }; use crate::BoxError; +use bitfinex::responses::OrderResponse; #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub enum Exchange { @@ -103,6 +102,18 @@ impl Client { pub async fn cancel_order(&self, order: &ActiveOrder) -> Result { self.inner.cancel_order(order).await } + + pub async fn transfer_between_wallets( + &self, + from: &WalletKind, + to: &WalletKind, + symbol: Symbol, + amount: f64, + ) -> Result<(), BoxError> { + self.inner + .transfer_between_wallets(from, to, symbol, amount) + .await + } } #[async_trait] @@ -114,6 +125,13 @@ pub trait Connector: Send + Sync { async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError>; async fn submit_order(&self, order: &OrderForm) -> Result; async fn cancel_order(&self, order: &ActiveOrder) -> Result; + async fn transfer_between_wallets( + &self, + from: &WalletKind, + to: &WalletKind, + symbol: Symbol, + amount: f64, + ) -> Result<(), BoxError>; } impl Debug for dyn Connector { @@ -305,6 +323,24 @@ impl Connector for BitfinexConnector { Ok((&response).try_into()?) } + + async fn transfer_between_wallets( + &self, + from: &WalletKind, + to: &WalletKind, + symbol: Symbol, + amount: f64, + ) -> Result<(), BoxError> { + let result = self + .bfx + .account + .transfer_between_wallets(from.into(), to.into(), symbol.to_string(), amount) + .await?; + + println!("{:?}", result); + + Ok(()) + } } impl From<&ActiveOrder> for CancelOrderForm { @@ -313,7 +349,7 @@ impl From<&ActiveOrder> for CancelOrderForm { } } -impl TryFrom<&bitfinex::orders::OrderResponse> for ActiveOrder { +impl TryFrom<&bitfinex::responses::OrderResponse> for ActiveOrder { type Error = BoxError; fn try_from(response: &OrderResponse) -> Result { @@ -387,7 +423,7 @@ impl From<&OrderForm> for bitfinex::orders::OrderKind { } } -impl From<&bitfinex::orders::OrderResponse> for TradingPlatform { +impl From<&bitfinex::responses::OrderResponse> for TradingPlatform { fn from(response: &OrderResponse) -> Self { match response.order_type() { bitfinex::orders::OrderKind::Limit @@ -417,7 +453,7 @@ impl From<&bitfinex::orders::ActiveOrder> for TradingPlatform { } } -impl From<&bitfinex::orders::OrderResponse> for OrderKind { +impl From<&bitfinex::responses::OrderResponse> for OrderKind { fn from(response: &OrderResponse) -> Self { match response.order_type() { bitfinex::orders::OrderKind::Limit | bitfinex::orders::OrderKind::ExchangeLimit => { @@ -544,3 +580,13 @@ impl From for PriceTicker { } } } + +impl From<&WalletKind> for &bitfinex::account::WalletKind { + fn from(k: &WalletKind) -> Self { + match k { + WalletKind::Exchange => &bitfinex::account::WalletKind::Exchange, + WalletKind::Margin => &bitfinex::account::WalletKind::Margin, + WalletKind::Funding => &bitfinex::account::WalletKind::Funding, + } + } +} diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index bb6e3f8..cc8485e 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -11,10 +11,11 @@ use tokio::sync::oneshot; use tokio::time::Duration; use crate::connectors::{Client, ExchangeDetails}; -use crate::currency::SymbolPair; +use crate::currency::{Symbol, SymbolPair}; use crate::events::{ActorMessage, Event, Message}; use crate::models::{ ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker, TradingPlatform, + WalletKind, }; use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop}; use crate::BoxError; diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index 04e833b..42cfa87 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -490,3 +490,9 @@ pub enum PositionState { Closed, Open, } + +pub enum WalletKind { + Exchange, + Margin, + Funding, +} -- 2.47.2 From d383328ebb9dea6ffb16dfcaa26d56e27e70cebc Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Thu, 28 Jan 2021 20:06:11 +0000 Subject: [PATCH 106/127] implemented trades from orders and orders history --- rustybot/src/connectors.rs | 243 ++++++++++++++++++++++++------------- rustybot/src/currency.rs | 4 +- rustybot/src/managers.rs | 2 +- rustybot/src/models.rs | 71 ++++++++++- rustybot/src/strategy.rs | 12 +- 5 files changed, 234 insertions(+), 98 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index cc60ea7..0bf4c7c 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -5,20 +5,23 @@ use std::sync::Arc; use async_trait::async_trait; use bitfinex::api::Bitfinex; +use bitfinex::book::{Book, BookPrecision}; use bitfinex::orders::{CancelOrderForm, OrderMeta}; +use bitfinex::responses::{OrderResponse, TradeResponse}; use bitfinex::ticker::TradingPairTicker; use futures_retry::{FutureRetry, RetryPolicy}; use log::trace; use tokio::macros::support::Future; use tokio::time::Duration; +use tokio_tungstenite::stream::Stream::Plain; use crate::currency::{Symbol, SymbolPair}; +use crate::models::TradingPlatform::Margin; use crate::models::{ - ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionState, - PriceTicker, TradingPlatform, WalletKind, + ActiveOrder, OrderBook, OrderBookEntry, OrderDetails, OrderFee, OrderForm, OrderKind, Position, + PositionState, PriceTicker, Trade, TradingPlatform, WalletKind, }; use crate::BoxError; -use bitfinex::responses::OrderResponse; #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub enum Exchange { @@ -114,6 +117,20 @@ impl Client { .transfer_between_wallets(from, to, symbol, amount) .await } + + pub async fn trades_from_order( + &self, + order: &OrderDetails, + ) -> Result>, BoxError> { + self.inner.trades_from_order(order).await + } + + pub async fn orders_history( + &self, + pair: &SymbolPair, + ) -> Result>, BoxError> { + self.inner.orders_history(pair).await + } } #[async_trait] @@ -132,6 +149,12 @@ pub trait Connector: Send + Sync { symbol: Symbol, amount: f64, ) -> Result<(), BoxError>; + async fn trades_from_order(&self, order: &OrderDetails) + -> Result>, BoxError>; + async fn orders_history( + &self, + pair: &SymbolPair, + ) -> Result>, BoxError>; } impl Debug for dyn Connector { @@ -171,6 +194,31 @@ impl BitfinexConnector { format!("{}{}", pair.base(), pair.quote()) } } + + // retry to submit the request until it succeeds. + // the function may fail due to concurrent signed requests + // parsed in different times by the server + async fn retry_nonce(mut func: F) -> Result + where + F: FnMut() -> Fut, + Fut: Future>, + { + let response = { + loop { + match func().await { + Ok(response) => break response, + Err(e) => { + if !e.to_string().contains("nonce: small") { + return Err(e); + } + tokio::time::sleep(Duration::from_nanos(1)).await; + } + } + } + }; + + Ok(response) + } } #[async_trait] @@ -180,12 +228,8 @@ impl Connector for BitfinexConnector { } async fn active_positions(&self, pair: &SymbolPair) -> Result>, BoxError> { - let (active_positions, _) = FutureRetry::new( - move || self.bfx.positions.active_positions(), - BitfinexConnector::handle_small_nonce_error, - ) - .await - .map_err(|(e, _attempts)| e)?; + let active_positions = + BitfinexConnector::retry_nonce(|| self.bfx.positions.active_positions()).await?; let positions: Vec<_> = active_positions .into_iter() @@ -205,13 +249,28 @@ impl Connector for BitfinexConnector { Ok(ticker) } + async fn order_book(&self, pair: &SymbolPair) -> Result { + let symbol_name = BitfinexConnector::format_trading_pair(pair); + + let response = BitfinexConnector::retry_nonce(|| { + self.bfx.book.trading_pair(&symbol_name, BookPrecision::P0) + }) + .await?; + + let entries = response + .into_iter() + .map(|x| OrderBookEntry::Trading { + price: x.price, + count: x.count as u64, + amount: x.amount, + }) + .collect(); + + Ok(OrderBook::new(pair.clone()).with_entries(entries)) + } + async fn active_orders(&self, _: &SymbolPair) -> Result, BoxError> { - let (response, _) = FutureRetry::new( - move || self.bfx.orders.active_orders(), - BitfinexConnector::handle_small_nonce_error, - ) - .await - .map_err(|(e, _attempts)| e)?; + let response = BitfinexConnector::retry_nonce(|| self.bfx.orders.active_orders()).await?; Ok(response.iter().map(Into::into).collect()) } @@ -250,76 +309,17 @@ impl Connector for BitfinexConnector { BitfinexConnector::AFFILIATE_CODE.to_string(), )); - // retry to submit the order until it succeeds. - // the function may fail due to concurrent signed requests - // parsed in different times by the server - let response = { - loop { - match self.bfx.orders.submit_order(&order_form).await { - Ok(response) => break response, - Err(e) => { - if !e.to_string().contains("nonce: small") { - return Err(e); - } - tokio::time::sleep(Duration::from_nanos(1)).await; - } - } - } - }; + let response = + BitfinexConnector::retry_nonce(|| self.bfx.orders.submit_order(&order_form)).await?; Ok((&response).try_into()?) } - async fn order_book(&self, pair: &SymbolPair) -> Result { - let symbol_name = BitfinexConnector::format_trading_pair(pair); - - let response = { - loop { - match self - .bfx - .book - .trading_pair(symbol_name.clone(), bitfinex::book::BookPrecision::P0) - .await - { - Ok(response) => break response, - Err(e) => { - if !e.to_string().contains("nonce: small") { - return Err(e); - } - tokio::time::sleep(Duration::from_nanos(1)).await; - } - } - } - }; - - let entries = response - .into_iter() - .map(|x| OrderBookEntry::Trading { - price: x.price, - count: x.count as u64, - amount: x.amount, - }) - .collect(); - - Ok(OrderBook::new(pair.clone()).with_entries(entries)) - } - async fn cancel_order(&self, order: &ActiveOrder) -> Result { let cancel_form = order.into(); - let response = { - loop { - match self.bfx.orders.cancel_order(&cancel_form).await { - Ok(response) => break response, - Err(e) => { - if !e.to_string().contains("nonce: small") { - return Err(e); - } - tokio::time::sleep(Duration::from_nanos(1)).await; - } - } - } - }; + let response = + BitfinexConnector::retry_nonce(|| self.bfx.orders.cancel_order(&cancel_form)).await?; Ok((&response).try_into()?) } @@ -331,16 +331,48 @@ impl Connector for BitfinexConnector { symbol: Symbol, amount: f64, ) -> Result<(), BoxError> { - let result = self - .bfx - .account - .transfer_between_wallets(from.into(), to.into(), symbol.to_string(), amount) - .await?; - - println!("{:?}", result); + BitfinexConnector::retry_nonce(|| { + self.bfx.account.transfer_between_wallets( + from.into(), + to.into(), + symbol.to_string(), + amount, + ) + }) + .await?; Ok(()) } + + async fn trades_from_order( + &self, + order: &OrderDetails, + ) -> Result>, BoxError> { + let response = BitfinexConnector::retry_nonce(|| { + self.bfx + .trades + .generated_by_order(order.pair().trading_repr(), order.id()) + }) + .await?; + + if response.is_empty() { + Ok(None) + } else { + Ok(Some(response.iter().map(Into::into).collect())) + } + } + + async fn orders_history( + &self, + pair: &SymbolPair, + ) -> Result>, BoxError> { + let response = + BitfinexConnector::retry_nonce(|| self.bfx.orders.history(Some(pair.trading_repr()))) + .await?; + let mapped_vec: Vec<_> = response.iter().map(Into::into).collect(); + + Ok((!mapped_vec.is_empty()).then_some(mapped_vec)) + } } impl From<&ActiveOrder> for CancelOrderForm { @@ -359,7 +391,7 @@ impl TryFrom<&bitfinex::responses::OrderResponse> for ActiveOrder { group_id: response.gid(), client_id: Some(response.cid()), symbol: SymbolPair::from_str(response.symbol())?, - current_form: OrderForm::new( + details: OrderForm::new( SymbolPair::from_str(response.symbol())?, response.into(), response.into(), @@ -557,7 +589,7 @@ impl From<&bitfinex::orders::ActiveOrder> for ActiveOrder { group_id: order.group_id().map(|x| x as u64), client_id: Some(order.client_id()), symbol: pair.clone(), - current_form: OrderForm::new(pair, order.into(), order.into()), + details: OrderForm::new(pair, order.into(), order.into()), creation_timestamp: order.creation_timestamp(), update_timestamp: order.update_timestamp(), } @@ -590,3 +622,40 @@ impl From<&WalletKind> for &bitfinex::account::WalletKind { } } } + +impl From<&bitfinex::orders::ActiveOrder> for OrderDetails { + fn from(order: &bitfinex::orders::ActiveOrder) -> Self { + Self::new( + Exchange::Bitfinex, + order.id(), + SymbolPair::from_str(order.symbol()).unwrap(), + order.into(), + order.into(), + order.update_timestamp(), + ) + } +} + +// TODO: fields are hardcoded, to fix +impl From<&bitfinex::responses::TradeResponse> for Trade { + fn from(response: &TradeResponse) -> Self { + let pair = SymbolPair::from_str(&response.symbol).unwrap(); + let fee = { + if response.is_maker { + OrderFee::Maker(response.fee) + } else { + OrderFee::Taker(response.fee) + } + }; + + Self { + trade_id: response.trade_id, + pair, + execution_timestamp: response.execution_timestamp, + price: response.execution_price, + amount: response.execution_amount, + fee, + fee_currency: Symbol::new(response.symbol.clone()), + } + } +} diff --git a/rustybot/src/currency.rs b/rustybot/src/currency.rs index 23ab15a..6f1d4a1 100644 --- a/rustybot/src/currency.rs +++ b/rustybot/src/currency.rs @@ -71,10 +71,10 @@ impl SymbolPair { SymbolPair { quote, base } } pub fn trading_repr(&self) -> String { - format!("t{}{}", self.quote, self.base) + format!("t{}{}", self.base, self.quote) } pub fn funding_repr(&self) -> String { - format!("f{}{}", self.quote, self.base) + format!("f{}{}", self.base, self.quote) } pub fn quote(&self) -> &Symbol { &self.quote diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index cc8485e..5b76e72 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -422,7 +422,7 @@ impl OrderManager { if let Some(position) = open_positions.into_iter().find(|x| x.id() == position_id) { let opt_position_order = open_orders .iter() - .find(|x| x.current_form.amount().neg() == position.amount()); + .find(|x| x.details.amount().neg() == position.amount()); // checking if the position has an open order. // If so, don't do anything since the order is taken care of diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index 42cfa87..1e137b5 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -3,7 +3,7 @@ use std::fmt::{Display, Formatter}; use std::hash::{Hash, Hasher}; use crate::connectors::Exchange; -use crate::currency::SymbolPair; +use crate::currency::{Symbol, SymbolPair}; /*************** * Prices @@ -104,6 +104,62 @@ impl OrderBook { } } +#[derive(Debug)] +pub enum OrderFee { + Maker(f64), + Taker(f64), +} + +#[derive(Debug)] +pub struct OrderDetails { + exchange: Exchange, + pair: SymbolPair, + platform: TradingPlatform, + kind: OrderKind, + execution_timestamp: u64, + id: u64, +} + +impl OrderDetails { + pub fn new( + exchange: Exchange, + id: u64, + pair: SymbolPair, + platform: TradingPlatform, + kind: OrderKind, + execution_timestamp: u64, + ) -> Self { + OrderDetails { + exchange, + pair, + platform, + kind, + execution_timestamp, + id, + } + } + + pub fn exchange(&self) -> Exchange { + self.exchange + } + pub fn platform(&self) -> TradingPlatform { + self.platform + } + pub fn kind(&self) -> OrderKind { + self.kind + } + pub fn execution_timestamp(&self) -> u64 { + self.execution_timestamp + } + pub fn id(&self) -> u64 { + self.id + } + + pub fn pair(&self) -> &SymbolPair { + &self.pair + } +} + #[derive(Clone, Debug)] pub struct ActiveOrder { pub(crate) exchange: Exchange, @@ -111,7 +167,7 @@ pub struct ActiveOrder { pub(crate) group_id: Option, pub(crate) client_id: Option, pub(crate) symbol: SymbolPair, - pub(crate) current_form: OrderForm, + pub(crate) details: OrderForm, pub(crate) creation_timestamp: u64, pub(crate) update_timestamp: u64, } @@ -496,3 +552,14 @@ pub enum WalletKind { Margin, Funding, } + +#[derive(Debug)] +pub struct Trade { + pub trade_id: u64, + pub pair: SymbolPair, + pub execution_timestamp: u64, + pub price: f64, + pub amount: f64, + pub fee: OrderFee, + pub fee_currency: Symbol, +} diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 8b88d48..df9a34a 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -75,9 +75,9 @@ pub struct TrailingStop { impl TrailingStop { const BREAK_EVEN_PERC: f64 = 0.1; - const MIN_PROFIT_PERC: f64 = 0.7; + const MIN_PROFIT_PERC: f64 = 0.5; const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 1.75; - const MAX_LOSS_PERC: f64 = -1.75; + const MAX_LOSS_PERC: f64 = -4.0; pub fn new() -> Self { TrailingStop { @@ -276,7 +276,7 @@ impl OrderStrategy for FastOrderStrategy { // long let offer_comparison = { - if order.current_form.amount() > 0.0 { + if order.details.amount() > 0.0 { order_book.highest_bid() } else { order_book.lowest_ask() @@ -286,7 +286,7 @@ impl OrderStrategy for FastOrderStrategy { // if the best offer is higher than our threshold, // ask the manager to close the position with a market order let order_price = order - .current_form + .details .price() .ok_or("The active order does not have a price!")?; let delta = (1.0 - (offer_comparison / order_price)).abs() * 100.0; @@ -296,9 +296,9 @@ impl OrderStrategy for FastOrderStrategy { order: OrderForm::new( order.symbol.clone(), OrderKind::Market { - amount: order.current_form.amount(), + amount: order.details.amount(), }, - order.current_form.platform().clone(), + order.details.platform().clone(), ), }) } -- 2.47.2 From 054b3b6659fbb18e01ef4d8cebcdf57b0d5fa0ee Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Thu, 28 Jan 2021 20:07:26 +0000 Subject: [PATCH 107/127] cargo fix and reformatting --- rustybot/src/connectors.rs | 6 ++---- rustybot/src/managers.rs | 3 +-- rustybot/src/ticker.rs | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 0bf4c7c..26aacfa 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -5,18 +5,16 @@ use std::sync::Arc; use async_trait::async_trait; use bitfinex::api::Bitfinex; -use bitfinex::book::{Book, BookPrecision}; +use bitfinex::book::BookPrecision; use bitfinex::orders::{CancelOrderForm, OrderMeta}; use bitfinex::responses::{OrderResponse, TradeResponse}; use bitfinex::ticker::TradingPairTicker; -use futures_retry::{FutureRetry, RetryPolicy}; +use futures_retry::RetryPolicy; use log::trace; use tokio::macros::support::Future; use tokio::time::Duration; -use tokio_tungstenite::stream::Stream::Plain; use crate::currency::{Symbol, SymbolPair}; -use crate::models::TradingPlatform::Margin; use crate::models::{ ActiveOrder, OrderBook, OrderBookEntry, OrderDetails, OrderFee, OrderForm, OrderKind, Position, PositionState, PriceTicker, Trade, TradingPlatform, WalletKind, diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 5b76e72..bf34e82 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -11,11 +11,10 @@ use tokio::sync::oneshot; use tokio::time::Duration; use crate::connectors::{Client, ExchangeDetails}; -use crate::currency::{Symbol, SymbolPair}; +use crate::currency::SymbolPair; use crate::events::{ActorMessage, Event, Message}; use crate::models::{ ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker, TradingPlatform, - WalletKind, }; use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop}; use crate::BoxError; diff --git a/rustybot/src/ticker.rs b/rustybot/src/ticker.rs index b5c036e..d02d5b6 100644 --- a/rustybot/src/ticker.rs +++ b/rustybot/src/ticker.rs @@ -1,4 +1,4 @@ -use tokio::time::{Duration}; +use tokio::time::Duration; pub struct Ticker { duration: Duration, -- 2.47.2 From 7ba90a72c0eb4de471b1772c00c04ba7e2056815 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Fri, 12 Feb 2021 14:56:30 +0000 Subject: [PATCH 108/127] code reorganization --- rustybot/src/managers.rs | 72 ++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index bf34e82..e62136b 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -33,40 +33,6 @@ pub struct PriceManager { client: Client, } -pub struct PriceManagerHandle { - sender: Sender, -} - -impl PriceManagerHandle { - async fn run_price_manager(mut manager: PriceManager) { - while let Some(msg) = manager.receiver.recv().await { - manager.handle_message(msg).await.unwrap(); - } - } - - pub fn new(pair: SymbolPair, client: Client) -> Self { - let (sender, receiver) = channel(1); - - let price_manager = PriceManager::new(receiver, pair, client); - tokio::spawn(PriceManagerHandle::run_price_manager(price_manager)); - - Self { sender } - } - - pub async fn update(&mut self, tick: u64) -> Result { - let (send, recv) = oneshot::channel(); - - self.sender - .send(ActorMessage { - message: Message::Update { tick }, - respond_to: send, - }) - .await?; - - Ok(recv.await?) - } -} - impl PriceManager { pub fn new(receiver: Receiver, pair: SymbolPair, client: Client) -> Self { PriceManager { @@ -112,6 +78,40 @@ impl PriceManager { } } +pub struct PriceManagerHandle { + sender: Sender, +} + +impl PriceManagerHandle { + async fn run_price_manager(mut manager: PriceManager) { + while let Some(msg) = manager.receiver.recv().await { + manager.handle_message(msg).await.unwrap(); + } + } + + pub fn new(pair: SymbolPair, client: Client) -> Self { + let (sender, receiver) = channel(1); + + let price_manager = PriceManager::new(receiver, pair, client); + tokio::spawn(PriceManagerHandle::run_price_manager(price_manager)); + + Self { sender } + } + + pub async fn update(&mut self, tick: u64) -> Result { + let (send, recv) = oneshot::channel(); + + self.sender + .send(ActorMessage { + message: Message::Update { tick }, + respond_to: send, + }) + .await?; + + Ok(recv.await?) + } +} + #[derive(Clone, Debug)] pub struct PriceEntry { tick: u64, @@ -431,14 +431,14 @@ impl OrderManager { // No open order, undercutting best price with limit order let closing_price = self.best_closing_price(&position, &order_book); - // TODO: hardcoded platform to Margin! + // TODO: hardcoded platform to Derivative! let order_form = OrderForm::new( self.pair.clone(), OrderKind::Limit { price: closing_price, amount: position.amount().neg(), }, - TradingPlatform::Margin, + TradingPlatform::Derivative, ); info!("Submitting {} order", order_form.kind()); -- 2.47.2 From 6c409ac9fd347481bc42b48ae5939f2c5a8fe0f9 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Fri, 12 Feb 2021 14:56:50 +0000 Subject: [PATCH 109/127] added trading fees model --- rustybot/src/models.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index 1e137b5..32c8743 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -563,3 +563,15 @@ pub struct Trade { pub fee: OrderFee, pub fee_currency: Symbol, } + +#[derive(Debug)] +pub enum TradingFees { + Maker { + platform: TradingPlatform, + percentage: f64, + }, + Taker { + platform: TradingPlatform, + percentage: f64, + }, +} -- 2.47.2 From 2acc81cd7521912049005a18f04f3bc9452a1670 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Fri, 12 Feb 2021 14:57:23 +0000 Subject: [PATCH 110/127] remodeled percentage calculation in strategy --- rustybot/src/strategy.rs | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index df9a34a..90aabef 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -4,8 +4,10 @@ use std::fmt::{Debug, Formatter}; use dyn_clone::DynClone; use log::info; +use crate::connectors::Connector; use crate::events::{Event, EventKind, EventMetadata, Message}; use crate::managers::OptionUpdate; +use crate::models::OrderBookEntry::Trading; use crate::models::{ ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionProfitState, PositionState, TradingPlatform, @@ -74,10 +76,22 @@ pub struct TrailingStop { } impl TrailingStop { - const BREAK_EVEN_PERC: f64 = 0.1; - const MIN_PROFIT_PERC: f64 = 0.5; - const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 1.75; - const MAX_LOSS_PERC: f64 = -4.0; + // in percentage + const CAPITAL_MAX_LOSS: f64 = 17.5; + const CAPITAL_MIN_PROFIT: f64 = 10.0; + const CAPITAL_GOOD_PROFIT: f64 = 20.0; + + // in percentage + const MIN_PROFIT_TRAILING_DELTA: f64 = 0.2; + const GOOD_PROFIT_TRAILING_DELTA: f64 = 0.1; + + const LEVERAGE: f64 = 15.0; + + const MIN_PROFIT_PERC: f64 = (TrailingStop::CAPITAL_MIN_PROFIT / TrailingStop::LEVERAGE) + + TrailingStop::MIN_PROFIT_TRAILING_DELTA; + const GOOD_PROFIT_PERC: f64 = (TrailingStop::CAPITAL_GOOD_PROFIT / TrailingStop::LEVERAGE) + + TrailingStop::GOOD_PROFIT_TRAILING_DELTA; + const MAX_LOSS_PERC: f64 = -(TrailingStop::CAPITAL_MAX_LOSS / TrailingStop::LEVERAGE); pub fn new() -> Self { TrailingStop { @@ -88,8 +102,8 @@ impl TrailingStop { fn update_stop_percentage(&mut self, position: &Position) { if let Some(profit_state) = position.profit_state() { let profit_state_delta = match profit_state { - PositionProfitState::MinimumProfit => Some(0.2), - PositionProfitState::Profit => Some(0.1), + PositionProfitState::MinimumProfit => Some(TrailingStop::MIN_PROFIT_TRAILING_DELTA), + PositionProfitState::Profit => Some(TrailingStop::GOOD_PROFIT_TRAILING_DELTA), _ => None, }; @@ -252,7 +266,7 @@ pub struct FastOrderStrategy { impl Default for FastOrderStrategy { fn default() -> Self { - Self { threshold: 0.2 } + Self { threshold: 0.15 } } } -- 2.47.2 From 613b31463130f694f88a75cbf6b3c2d408f7f7dd Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Fri, 12 Feb 2021 14:57:37 +0000 Subject: [PATCH 111/127] added derivative symbols --- rustybot/src/currency.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rustybot/src/currency.rs b/rustybot/src/currency.rs index 6f1d4a1..58d4f4c 100644 --- a/rustybot/src/currency.rs +++ b/rustybot/src/currency.rs @@ -28,6 +28,10 @@ impl Symbol { pub const LTC: Symbol = Symbol::new_static("LTC"); pub const DOT: Symbol = Symbol::new_static("DOT"); + pub const DERIV_BTC: Symbol = Symbol::new_static("BTCF0"); + pub const DERIV_ETH: Symbol = Symbol::new_static("ETHF0"); + pub const DERIV_USDT: Symbol = Symbol::new_static("USTF0"); + // Paper trading pub const TESTBTC: Symbol = Symbol::new_static("TESTBTC"); pub const TESTUSD: Symbol = Symbol::new_static("TESTUSD"); -- 2.47.2 From e21080898337b66f2cbed9cd91b2b670cd01bfb0 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Fri, 12 Feb 2021 14:58:43 +0000 Subject: [PATCH 112/127] added support for derivative trading in connectors. stub for fees calculation. hardcoded leverage 15 in order submission --- rustybot/src/connectors.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 26aacfa..4757f7f 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -17,7 +17,7 @@ use tokio::time::Duration; use crate::currency::{Symbol, SymbolPair}; use crate::models::{ ActiveOrder, OrderBook, OrderBookEntry, OrderDetails, OrderFee, OrderForm, OrderKind, Position, - PositionState, PriceTicker, Trade, TradingPlatform, WalletKind, + PositionState, PriceTicker, Trade, TradingFees, TradingPlatform, WalletKind, }; use crate::BoxError; @@ -69,9 +69,9 @@ impl Client { // TODO: change fee with account's taker fee positions.iter_mut().flatten().for_each(|x| { if x.is_short() { - x.update_profit_loss(best_ask, 0.2); + x.update_profit_loss(best_ask, 0.075); } else { - x.update_profit_loss(best_bid, 0.2); + x.update_profit_loss(best_bid, 0.075); } }); @@ -153,6 +153,7 @@ pub trait Connector: Send + Sync { &self, pair: &SymbolPair, ) -> Result>, BoxError>; + async fn trading_fees(&self) -> Result, BoxError>; } impl Debug for dyn Connector { @@ -189,7 +190,11 @@ impl BitfinexConnector { if pair.to_string().to_lowercase().contains("test") { format!("{}:{}", pair.base(), pair.quote()) } else { - format!("{}{}", pair.base(), pair.quote()) + if pair.to_string().to_lowercase().contains("f0") { + format!("{}:{}", pair.base(), pair.quote()) + } else { + format!("{}{}", pair.base(), pair.quote()) + } } } @@ -279,9 +284,11 @@ impl Connector for BitfinexConnector { let order_form = match order.kind() { OrderKind::Limit { price, amount } => { bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) + .with_leverage(15) } - OrderKind::Market { amount } => { + OrderKind::Market { amount, .. } => { bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into()) + .with_leverage(15) } OrderKind::Stop { price, amount } => { bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) @@ -371,6 +378,10 @@ impl Connector for BitfinexConnector { Ok((!mapped_vec.is_empty()).then_some(mapped_vec)) } + + async fn trading_fees(&self) -> Result, BoxError> { + unimplemented!() + } } impl From<&ActiveOrder> for CancelOrderForm { @@ -439,7 +450,7 @@ impl From<&OrderForm> for bitfinex::orders::OrderKind { OrderKind::FillOrKill { .. } => bitfinex::orders::OrderKind::ExchangeFok, OrderKind::ImmediateOrCancel { .. } => bitfinex::orders::OrderKind::ExchangeIoc, }, - TradingPlatform::Margin => match o.kind() { + TradingPlatform::Margin | TradingPlatform::Derivative => match o.kind() { OrderKind::Limit { .. } => bitfinex::orders::OrderKind::Limit, OrderKind::Market { .. } => bitfinex::orders::OrderKind::Market, OrderKind::Stop { .. } => bitfinex::orders::OrderKind::Stop, -- 2.47.2 From b46aec3395dc936c54fe54ab2db8859e3292ea14 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sat, 13 Feb 2021 13:49:06 +0000 Subject: [PATCH 113/127] use dotenv --- rustybot/Cargo.lock | 194 +++++++++++-------------------------------- rustybot/Cargo.toml | 3 +- rustybot/src/main.rs | 36 ++++---- 3 files changed, 66 insertions(+), 167 deletions(-) diff --git a/rustybot/Cargo.lock b/rustybot/Cargo.lock index cba87b0..82c4d3c 100644 --- a/rustybot/Cargo.lock +++ b/rustybot/Cargo.lock @@ -87,12 +87,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" - [[package]] name = "base64" version = "0.12.3" @@ -121,7 +115,7 @@ dependencies = [ "serde_derive", "serde_json", "tokio 1.1.0", - "tungstenite 0.9.2", + "tungstenite 0.12.0", "url", ] @@ -131,34 +125,13 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -[[package]] -name = "block-buffer" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -dependencies = [ - "block-padding", - "byte-tools", - "byteorder", - "generic-array 0.12.3", -] - [[package]] name = "block-buffer" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array 0.14.4", -] - -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", + "generic-array", ] [[package]] @@ -167,28 +140,12 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - [[package]] name = "byteorder" version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" -[[package]] -name = "bytes" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" -dependencies = [ - "byteorder", - "iovec", -] - [[package]] name = "bytes" version = "0.5.6" @@ -271,24 +228,21 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array 0.12.3", -] - [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.4", + "generic-array", ] +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + [[package]] name = "dyn-clone" version = "1.0.4" @@ -314,12 +268,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - [[package]] name = "fern" version = "0.6.0" @@ -476,15 +424,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generic-array" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" -dependencies = [ - "typenum", -] - [[package]] name = "generic-array" version = "0.14.4" @@ -534,7 +473,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http 0.2.2", + "http", "indexmap", "slab", "tokio 1.1.0", @@ -564,17 +503,6 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" -[[package]] -name = "http" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0" -dependencies = [ - "bytes 0.4.12", - "fnv", - "itoa", -] - [[package]] name = "http" version = "0.2.2" @@ -593,7 +521,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2861bd27ee074e5ee891e8b539837a9430012e249d7f0ca2d795650f579c1994" dependencies = [ "bytes 1.0.1", - "http 0.2.2", + "http", ] [[package]] @@ -619,7 +547,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http 0.2.2", + "http", "http-body", "httparse", "httpdate", @@ -666,15 +594,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "input_buffer" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1b822cc844905551931d6f81608ed5f50a79c1078a4e2b4d42dbc7c1eedfbf" -dependencies = [ - "bytes 0.4.12", -] - [[package]] name = "input_buffer" version = "0.3.1" @@ -684,6 +603,15 @@ dependencies = [ "bytes 0.5.6", ] +[[package]] +name = "input_buffer" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" +dependencies = [ + "bytes 1.0.1", +] + [[package]] name = "instant" version = "0.1.9" @@ -693,15 +621,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", -] - [[package]] name = "ipnet" version = "2.3.0" @@ -903,12 +822,6 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - [[package]] name = "opaque-debug" version = "0.3.0" @@ -1222,7 +1135,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "http 0.2.2", + "http", "http-body", "hyper", "hyper-tls", @@ -1274,6 +1187,7 @@ dependencies = [ "bitfinex", "byteorder", "chrono", + "dotenv", "dyn-clone", "fern", "float-cmp", @@ -1371,29 +1285,17 @@ dependencies = [ "serde", ] -[[package]] -name = "sha-1" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" -dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug 0.2.3", -] - [[package]] name = "sha-1" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c" dependencies = [ - "block-buffer 0.9.0", + "block-buffer", "cfg-if 1.0.0", "cpuid-bool", - "digest 0.9.0", - "opaque-debug 0.3.0", + "digest", + "opaque-debug", ] [[package]] @@ -1631,26 +1533,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" -[[package]] -name = "tungstenite" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0c2bd5aeb7dcd2bb32e472c8872759308495e5eccc942e929a513cd8d36110" -dependencies = [ - "base64 0.11.0", - "byteorder", - "bytes 0.4.12", - "http 0.1.21", - "httparse", - "input_buffer 0.2.0", - "log 0.4.11", - "native-tls", - "rand 0.7.3", - "sha-1 0.8.2", - "url", - "utf-8", -] - [[package]] name = "tungstenite" version = "0.11.1" @@ -1660,12 +1542,32 @@ dependencies = [ "base64 0.12.3", "byteorder", "bytes 0.5.6", - "http 0.2.2", + "http", "httparse", "input_buffer 0.3.1", "log 0.4.11", "rand 0.7.3", - "sha-1 0.9.2", + "sha-1", + "url", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" +dependencies = [ + "base64 0.13.0", + "byteorder", + "bytes 1.0.1", + "http", + "httparse", + "input_buffer 0.4.0", + "log 0.4.11", + "native-tls", + "rand 0.8.2", + "sha-1", "url", "utf-8", ] diff --git a/rustybot/Cargo.toml b/rustybot/Cargo.toml index b22004a..5f31416 100644 --- a/rustybot/Cargo.toml +++ b/rustybot/Cargo.toml @@ -20,4 +20,5 @@ chrono = "0.4" byteorder = "1" float-cmp = "0.8" merge = "0.1" -futures-retry = "0.6" \ No newline at end of file +futures-retry = "0.6" +dotenv = "0.15" \ No newline at end of file diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index 8a0d3e5..3f76b66 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -1,6 +1,8 @@ #![feature(drain_filter)] #![feature(bool_to_option)] +use std::env; + use fern::colors::{Color, ColoredLevelConfig}; use log::LevelFilter::{Debug, Trace}; use tokio::time::Duration; @@ -23,33 +25,27 @@ pub type BoxError = Box; #[tokio::main] async fn main() -> Result<(), BoxError> { setup_logger()?; + dotenv::dotenv()?; - // TEST - let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2"; - let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli"; - - // REAL - // let orders_api_key = "hc5nDvYbFYJZMKdnzYq8P4AzCSwjxfQHnMyrg69Sf4c"; - // let orders_api_secret = "53x9goIOpbOtBoPi7dmigK5Cq5e0282EUO2qRIMEXlh"; - // let prices_api_key = "gTfFZUCwRBE0Z9FZjyk9HNe4lZ7XuiZY9rrW71SyUr9"; - // let prices_api_secret = "zWbxvoFZad3BPIiXK4DKfEvC0YsAuaApbeAyI8OBXgN"; - // let positions_api_key = "PfR7BadPZPNdVZnkHFBfAjsg7gjt8pAecMj5B8eRPFi"; - // let positions_api_secret = "izzvxtE3XsBBRpVCHGJ8f60UA56SmPNbBvJGVd67aqD"; + let api_key = env::vars() + .find(|(k, v)| k == "API_KEY") + .map(|(k, v)| v) + .ok_or("API_KEY not set!")?; + let api_secret = env::vars() + .find(|(k, v)| k == "API_SECRET") + .map(|(k, v)| v) + .ok_or("API_SECRET not set!")?; let bitfinex = ExchangeDetails::Bitfinex { - prices_api_key: test_api_key.into(), - prices_api_secret: test_api_secret.into(), - orders_api_key: test_api_key.into(), - orders_api_secret: test_api_secret.into(), - positions_api_key: test_api_key.into(), - positions_api_secret: test_api_secret.into(), + api_key: api_key.into(), + api_secret: api_secret.into(), }; let mut bot = BfxBot::new( vec![bitfinex], - vec![Symbol::BTC, Symbol::ETH, Symbol::XMR], - Symbol::USD, - Duration::new(1, 0), + vec![Symbol::DERIV_ETH, Symbol::DERIV_BTC], + Symbol::DERIV_USDT, + Duration::new(10, 0), ); Ok(bot.start_loop().await?) -- 2.47.2 From ce8eec71ff2aa56831cc1cb86ae7fb6f22b6992d Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sat, 13 Feb 2021 14:58:15 +0000 Subject: [PATCH 114/127] fetch account fees on-demand and apply them to position p/l --- rustybot/src/connectors.rs | 111 +++++++++++++++++++++++++++++++------ rustybot/src/models.rs | 8 ++- 2 files changed, 102 insertions(+), 17 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 4757f7f..0a32b6c 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -57,21 +57,52 @@ impl Client { pair: &SymbolPair, ) -> Result>, BoxError> { // retrieving open positions and order book to calculate effective profit/loss - let (positions, order_book) = tokio::join!( + let (positions, order_book, fees) = tokio::join!( self.inner.active_positions(pair), - self.inner.order_book(pair) + self.inner.order_book(pair), + self.inner.trading_fees() ); - let (mut positions, order_book) = (positions?, order_book?); + let (mut positions, order_book, fees) = (positions?, order_book?, fees?); let (best_ask, best_bid) = (order_book.lowest_ask(), order_book.highest_bid()); + let derivative_taker = fees + .iter() + .filter_map(|x| match x { + TradingFees::Taker { + platform, + percentage, + } if platform == &TradingPlatform::Derivative => Some(percentage), + _ => None, + }) + .next() + .ok_or("Could not retrieve derivative taker fee!")?; + let margin_taker = fees + .iter() + .filter_map(|x| match x { + TradingFees::Taker { + platform, + percentage, + } if platform == &TradingPlatform::Margin => Some(percentage), + _ => None, + }) + .next() + .ok_or("Could not retrieve margin taker fee!")?; + // updating positions with effective profit/loss - // TODO: change fee with account's taker fee positions.iter_mut().flatten().for_each(|x| { + let fee = match x.platform() { + TradingPlatform::Funding | TradingPlatform::Exchange => { + unimplemented!() + } + TradingPlatform::Margin => margin_taker, + TradingPlatform::Derivative => derivative_taker, + }; + if x.is_short() { - x.update_profit_loss(best_ask, 0.075); + x.update_profit_loss(best_ask, *fee); } else { - x.update_profit_loss(best_bid, 0.075); + x.update_profit_loss(best_bid, *fee); } }); @@ -380,7 +411,46 @@ impl Connector for BitfinexConnector { } async fn trading_fees(&self) -> Result, BoxError> { - unimplemented!() + let mut fees = vec![]; + let accountfees = self.bfx.account.account_summary().await?; + + // Derivatives + let derivative_taker = TradingFees::Taker { + platform: TradingPlatform::Derivative, + percentage: accountfees.derivative_taker() * 100.0, + }; + let derivative_maker = TradingFees::Maker { + platform: TradingPlatform::Derivative, + percentage: accountfees.derivative_rebate() * 100.0, + }; + fees.push(derivative_taker); + fees.push(derivative_maker); + + // Exchange + let exchange_taker = TradingFees::Taker { + platform: TradingPlatform::Exchange, + percentage: accountfees.taker_to_fiat() * 100.0, + }; + let exchange_maker = TradingFees::Maker { + platform: TradingPlatform::Exchange, + percentage: accountfees.maker_fee() * 100.0, + }; + fees.push(exchange_taker); + fees.push(exchange_maker); + + // Margin + let margin_taker = TradingFees::Taker { + platform: TradingPlatform::Margin, + percentage: accountfees.taker_to_fiat() * 100.0, + }; + let margin_maker = TradingFees::Maker { + platform: TradingPlatform::Margin, + percentage: accountfees.maker_fee() * 100.0, + }; + fees.push(margin_taker); + fees.push(margin_maker); + + Ok(fees) } } @@ -423,6 +493,14 @@ impl TryInto for bitfinex::positions::Position { } }; + let platform = { + if self.symbol().to_ascii_lowercase().contains("f0") { + TradingPlatform::Derivative + } else { + TradingPlatform::Margin + } + }; + Ok(Position::new( SymbolPair::from_str(self.symbol())?, state, @@ -432,6 +510,7 @@ impl TryInto for bitfinex::positions::Position { self.pl_perc(), self.price_liq(), self.position_id(), + platform, ) .with_creation_date(self.mts_create()) .with_creation_update(self.mts_update())) @@ -648,23 +727,23 @@ impl From<&bitfinex::orders::ActiveOrder> for OrderDetails { // TODO: fields are hardcoded, to fix impl From<&bitfinex::responses::TradeResponse> for Trade { fn from(response: &TradeResponse) -> Self { - let pair = SymbolPair::from_str(&response.symbol).unwrap(); + let pair = SymbolPair::from_str(&response.symbol()).unwrap(); let fee = { - if response.is_maker { - OrderFee::Maker(response.fee) + if response.is_maker() { + OrderFee::Maker(response.fee()) } else { - OrderFee::Taker(response.fee) + OrderFee::Taker(response.fee()) } }; Self { - trade_id: response.trade_id, + trade_id: response.trade_id(), pair, - execution_timestamp: response.execution_timestamp, - price: response.execution_price, - amount: response.execution_amount, + execution_timestamp: response.execution_timestamp(), + price: response.execution_price(), + amount: response.execution_amount(), fee, - fee_currency: Symbol::new(response.symbol.clone()), + fee_currency: Symbol::new(response.symbol().to_owned().clone()), } } } diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index 32c8743..305d52b 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -186,7 +186,7 @@ impl PartialEq for ActiveOrder { impl Eq for ActiveOrder {} -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum TradingPlatform { Exchange, Derivative, @@ -396,6 +396,7 @@ pub struct Position { position_id: u64, creation_date: Option, creation_update: Option, + platform: TradingPlatform, } impl Position { @@ -408,6 +409,7 @@ impl Position { pl_perc: f64, price_liq: f64, position_id: u64, + platform: TradingPlatform, ) -> Self { Position { pair, @@ -421,6 +423,7 @@ impl Position { creation_date: None, creation_update: None, profit_state: None, + platform, } } @@ -505,6 +508,9 @@ impl Position { pub fn is_long(&self) -> bool { self.amount.is_sign_positive() } + pub fn platform(&self) -> TradingPlatform { + self.platform + } } impl Hash for Position { -- 2.47.2 From 127ffaa1b958fef2faecc12df74a564cb2fc6a5c Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sat, 13 Feb 2021 15:29:00 +0000 Subject: [PATCH 115/127] moved amount out of OrderKind and into OrderForm. leverage detection for open positions --- rustybot/src/connectors.rs | 93 ++++++++++++------------ rustybot/src/managers.rs | 5 +- rustybot/src/models.rs | 140 +++++++++++++------------------------ rustybot/src/strategy.rs | 7 +- 4 files changed, 99 insertions(+), 146 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 0a32b6c..f1da7ef 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -311,39 +311,46 @@ impl Connector for BitfinexConnector { async fn submit_order(&self, order: &OrderForm) -> Result { let symbol_name = format!("t{}", BitfinexConnector::format_trading_pair(order.pair())); + let amount = order.amount(); - let order_form = match order.kind() { - OrderKind::Limit { price, amount } => { - bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) - .with_leverage(15) + let order_form = { + let pre_leverage = { + match order.kind() { + OrderKind::Limit { price } => { + bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) + } + OrderKind::Market => { + bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into()) + } + OrderKind::Stop { price } => { + bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) + } + OrderKind::StopLimit { price, limit_price } => { + bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) + .with_price_aux_limit(limit_price)? + } + OrderKind::TrailingStop { distance } => { + bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into()) + .with_price_trailing(distance)? + } + OrderKind::FillOrKill { price } => { + bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) + } + OrderKind::ImmediateOrCancel { price } => { + bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) + } + } + .with_meta(OrderMeta::new( + BitfinexConnector::AFFILIATE_CODE.to_string(), + )) + }; + + // adding leverage, if any + match order.leverage() { + Some(leverage) => pre_leverage.with_leverage(leverage as u32), + None => pre_leverage, } - OrderKind::Market { amount, .. } => { - bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into()) - .with_leverage(15) - } - OrderKind::Stop { price, amount } => { - bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) - } - OrderKind::StopLimit { - price, - amount, - limit_price, - } => bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) - .with_price_aux_limit(limit_price)?, - OrderKind::TrailingStop { distance, amount } => { - bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into()) - .with_price_trailing(distance)? - } - OrderKind::FillOrKill { price, amount } => { - bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) - } - OrderKind::ImmediateOrCancel { price, amount } => { - bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into()) - } - } - .with_meta(OrderMeta::new( - BitfinexConnector::AFFILIATE_CODE.to_string(), - )); + }; let response = BitfinexConnector::retry_nonce(|| self.bfx.orders.submit_order(&order_form)).await?; @@ -474,6 +481,7 @@ impl TryFrom<&bitfinex::responses::OrderResponse> for ActiveOrder { SymbolPair::from_str(response.symbol())?, response.into(), response.into(), + response.amount(), ), creation_timestamp: 0, update_timestamp: 0, @@ -511,6 +519,7 @@ impl TryInto for bitfinex::positions::Position { self.price_liq(), self.position_id(), platform, + self.leverage(), ) .with_creation_date(self.mts_create()) .with_creation_update(self.mts_update())) @@ -579,41 +588,33 @@ impl From<&bitfinex::responses::OrderResponse> for OrderKind { bitfinex::orders::OrderKind::Limit | bitfinex::orders::OrderKind::ExchangeLimit => { Self::Limit { price: response.price(), - amount: response.amount(), } } bitfinex::orders::OrderKind::Market | bitfinex::orders::OrderKind::ExchangeMarket => { - Self::Market { - amount: response.amount(), - } + Self::Market } bitfinex::orders::OrderKind::Stop | bitfinex::orders::OrderKind::ExchangeStop => { Self::Stop { price: response.price(), - amount: response.amount(), } } bitfinex::orders::OrderKind::StopLimit | bitfinex::orders::OrderKind::ExchangeStopLimit => Self::StopLimit { price: response.price(), - amount: response.amount(), limit_price: response.price_aux_limit().expect("Limit price not found!"), }, bitfinex::orders::OrderKind::TrailingStop | bitfinex::orders::OrderKind::ExchangeTrailingStop => Self::TrailingStop { distance: response.price_trailing().expect("Distance not found!"), - amount: response.amount(), }, bitfinex::orders::OrderKind::Fok | bitfinex::orders::OrderKind::ExchangeFok => { Self::FillOrKill { price: response.price(), - amount: response.amount(), } } bitfinex::orders::OrderKind::Ioc | bitfinex::orders::OrderKind::ExchangeIoc => { Self::ImmediateOrCancel { price: response.price(), - amount: response.amount(), } } } @@ -626,41 +627,33 @@ impl From<&bitfinex::orders::ActiveOrder> for OrderKind { bitfinex::orders::OrderKind::Limit | bitfinex::orders::OrderKind::ExchangeLimit => { Self::Limit { price: response.price(), - amount: response.amount(), } } bitfinex::orders::OrderKind::Market | bitfinex::orders::OrderKind::ExchangeMarket => { - Self::Market { - amount: response.amount(), - } + Self::Market {} } bitfinex::orders::OrderKind::Stop | bitfinex::orders::OrderKind::ExchangeStop => { Self::Stop { price: response.price(), - amount: response.amount(), } } bitfinex::orders::OrderKind::StopLimit | bitfinex::orders::OrderKind::ExchangeStopLimit => Self::StopLimit { price: response.price(), - amount: response.amount(), limit_price: response.price_aux_limit().expect("Limit price not found!"), }, bitfinex::orders::OrderKind::TrailingStop | bitfinex::orders::OrderKind::ExchangeTrailingStop => Self::TrailingStop { distance: response.price_trailing().expect("Distance not found!"), - amount: response.amount(), }, bitfinex::orders::OrderKind::Fok | bitfinex::orders::OrderKind::ExchangeFok => { Self::FillOrKill { price: response.price(), - amount: response.amount(), } } bitfinex::orders::OrderKind::Ioc | bitfinex::orders::OrderKind::ExchangeIoc => { Self::ImmediateOrCancel { price: response.price(), - amount: response.amount(), } } } @@ -677,7 +670,7 @@ impl From<&bitfinex::orders::ActiveOrder> for ActiveOrder { group_id: order.group_id().map(|x| x as u64), client_id: Some(order.client_id()), symbol: pair.clone(), - details: OrderForm::new(pair, order.into(), order.into()), + details: OrderForm::new(pair, order.into(), order.into(), order.amount()), creation_timestamp: order.creation_timestamp(), update_timestamp: order.update_timestamp(), } diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index e62136b..6802f8d 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -436,10 +436,11 @@ impl OrderManager { self.pair.clone(), OrderKind::Limit { price: closing_price, - amount: position.amount().neg(), }, TradingPlatform::Derivative, - ); + position.amount().neg(), + ) + .with_leverage(position.leverage()); info!("Submitting {} order", order_form.kind()); if let Err(e) = self.client.submit_order(&order_form).await { diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index 305d52b..2b6d064 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -213,34 +213,13 @@ impl Display for TradingPlatform { #[derive(Copy, Clone, Debug)] pub enum OrderKind { - Limit { - price: f64, - amount: f64, - }, - Market { - amount: f64, - }, - Stop { - price: f64, - amount: f64, - }, - StopLimit { - price: f64, - amount: f64, - limit_price: f64, - }, - TrailingStop { - distance: f64, - amount: f64, - }, - FillOrKill { - price: f64, - amount: f64, - }, - ImmediateOrCancel { - price: f64, - amount: f64, - }, + Limit { price: f64 }, + Market, + Stop { price: f64 }, + StopLimit { price: f64, limit_price: f64 }, + TrailingStop { distance: f64 }, + FillOrKill { price: f64 }, + ImmediateOrCancel { price: f64 }, } impl OrderKind { @@ -260,67 +239,32 @@ impl OrderKind { impl Display for OrderKind { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - OrderKind::Limit { price, amount } => { + OrderKind::Limit { price } => { + write!(f, "[{} | Price: {:0.5}]", self.as_str(), price,) + } + OrderKind::Market => { + write!(f, "[{}]", self.as_str()) + } + OrderKind::Stop { price } => { + write!(f, "[{} | Price: {:0.5}", self.as_str(), price,) + } + OrderKind::StopLimit { price, limit_price } => { write!( f, - "[{} | Price: {:0.5}, Amount: {:0.5}]", + "[{} | Price: {:0.5}, Limit Price: {:0.5}]", self.as_str(), price, - amount - ) - } - OrderKind::Market { amount } => { - write!(f, "[{} | Amount: {:0.5}]", self.as_str(), amount) - } - OrderKind::Stop { price, amount } => { - write!( - f, - "[{} | Price: {:0.5}, Amount: {:0.5}]", - self.as_str(), - price, - amount - ) - } - OrderKind::StopLimit { - price, - amount, - limit_price, - } => { - write!( - f, - "[{} | Price: {:0.5}, Amount: {:0.5}, Limit Price: {:0.5}]", - self.as_str(), - price, - amount, limit_price ) } - OrderKind::TrailingStop { distance, amount } => { - write!( - f, - "[{} | Distance: {:0.5}, Amount: {:0.5}]", - self.as_str(), - distance, - amount - ) + OrderKind::TrailingStop { distance } => { + write!(f, "[{} | Distance: {:0.5}]", self.as_str(), distance,) } - OrderKind::FillOrKill { price, amount } => { - write!( - f, - "[{} | Price: {:0.5}, Amount: {:0.5}]", - self.as_str(), - price, - amount - ) + OrderKind::FillOrKill { price } => { + write!(f, "[{} | Price: {:0.5}]", self.as_str(), price,) } - OrderKind::ImmediateOrCancel { price, amount } => { - write!( - f, - "[{} | Price: {:0.5}, Amount: {:0.5}]", - self.as_str(), - price, - amount - ) + OrderKind::ImmediateOrCancel { price } => { + write!(f, "[{} | Price: {:0.5}]", self.as_str(), price,) } } } @@ -331,17 +275,31 @@ pub struct OrderForm { pair: SymbolPair, kind: OrderKind, platform: TradingPlatform, + amount: f64, + leverage: Option, } impl OrderForm { - pub fn new(pair: SymbolPair, order_kind: OrderKind, platform: TradingPlatform) -> Self { + pub fn new( + pair: SymbolPair, + order_kind: OrderKind, + platform: TradingPlatform, + amount: f64, + ) -> Self { Self { pair, kind: order_kind, platform, + amount, + leverage: None, } } + pub fn with_leverage(mut self, leverage: f64) -> Self { + self.leverage = Some(leverage); + self + } + pub fn pair(&self) -> &SymbolPair { &self.pair } @@ -355,15 +313,7 @@ impl OrderForm { } pub fn amount(&self) -> f64 { - match self.kind { - OrderKind::Limit { amount, .. } => amount, - OrderKind::Market { amount } => amount, - OrderKind::Stop { amount, .. } => amount, - OrderKind::StopLimit { amount, .. } => amount, - OrderKind::TrailingStop { amount, .. } => amount, - OrderKind::FillOrKill { amount, .. } => amount, - OrderKind::ImmediateOrCancel { amount, .. } => amount, - } + self.amount } pub fn price(&self) -> Option { @@ -377,6 +327,10 @@ impl OrderForm { OrderKind::ImmediateOrCancel { price, .. } => Some(price), } } + + pub fn leverage(&self) -> Option { + self.leverage + } } /*************** @@ -397,6 +351,7 @@ pub struct Position { creation_date: Option, creation_update: Option, platform: TradingPlatform, + leverage: f64, } impl Position { @@ -410,6 +365,7 @@ impl Position { price_liq: f64, position_id: u64, platform: TradingPlatform, + leverage: f64, ) -> Self { Position { pair, @@ -424,6 +380,7 @@ impl Position { creation_update: None, profit_state: None, platform, + leverage, } } @@ -511,6 +468,9 @@ impl Position { pub fn platform(&self) -> TradingPlatform { self.platform } + pub fn leverage(&self) -> f64 { + self.leverage + } } impl Hash for Position { diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 90aabef..a7c2064 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -10,7 +10,7 @@ use crate::managers::OptionUpdate; use crate::models::OrderBookEntry::Trading; use crate::models::{ ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionProfitState, - PositionState, TradingPlatform, + PositionState, TradingFees, TradingPlatform, }; use crate::BoxError; @@ -309,10 +309,9 @@ impl OrderStrategy for FastOrderStrategy { messages.push(Message::SubmitOrder { order: OrderForm::new( order.symbol.clone(), - OrderKind::Market { - amount: order.details.amount(), - }, + OrderKind::Market {}, order.details.platform().clone(), + order.details.amount(), ), }) } -- 2.47.2 From 597dc57bd5c811f94b53c8886e97119eedca84e6 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sat, 13 Feb 2021 15:31:19 +0000 Subject: [PATCH 116/127] cargo fix --- rustybot/src/strategy.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index a7c2064..af84cd0 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -7,7 +7,7 @@ use log::info; use crate::connectors::Connector; use crate::events::{Event, EventKind, EventMetadata, Message}; use crate::managers::OptionUpdate; -use crate::models::OrderBookEntry::Trading; + use crate::models::{ ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionProfitState, PositionState, TradingFees, TradingPlatform, -- 2.47.2 From e1330928310ffd96de3797ded4b46eee9401ce81 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sat, 13 Feb 2021 15:52:50 +0000 Subject: [PATCH 117/127] Thank you Clippy! --- rustybot/src/connectors.rs | 14 ++++---- rustybot/src/managers.rs | 72 ++++++++++++++------------------------ rustybot/src/models.rs | 23 ------------ rustybot/src/strategy.rs | 33 +++++------------ 4 files changed, 42 insertions(+), 100 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index f1da7ef..3d18dcf 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -208,7 +208,7 @@ impl BitfinexConnector { if e.to_string().contains("nonce: small") { return RetryPolicy::WaitRetry(Duration::from_millis(1)); } - return RetryPolicy::ForwardError(e); + RetryPolicy::ForwardError(e) } pub fn new(api_key: &str, api_secret: &str) -> Self { @@ -218,14 +218,12 @@ impl BitfinexConnector { } fn format_trading_pair(pair: &SymbolPair) -> String { - if pair.to_string().to_lowercase().contains("test") { + if pair.to_string().to_lowercase().contains("test") + || pair.to_string().to_lowercase().contains("f0") + { format!("{}:{}", pair.base(), pair.quote()) } else { - if pair.to_string().to_lowercase().contains("f0") { - format!("{}:{}", pair.base(), pair.quote()) - } else { - format!("{}{}", pair.base(), pair.quote()) - } + format!("{}{}", pair.base(), pair.quote()) } } @@ -736,7 +734,7 @@ impl From<&bitfinex::responses::TradeResponse> for Trade { price: response.execution_price(), amount: response.execution_amount(), fee, - fee_currency: Symbol::new(response.symbol().to_owned().clone()), + fee_currency: Symbol::new(response.symbol().to_owned()), } } } diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 6802f8d..77fe2c1 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -44,12 +44,9 @@ impl PriceManager { } pub async fn handle_message(&mut self, message: ActorMessage) -> Result<(), BoxError> { - match message.message { - Message::Update { tick } => { - let a = self.update(tick).await?; - self.add_entry(a); - } - _ => {} + if let Message::Update { tick } = message.message { + let a = self.update(tick).await?; + self.add_entry(a); } Ok(message @@ -247,11 +244,7 @@ impl PositionManager { Some(positions) => { // checking if there are positions open for our pair - match positions - .into_iter() - .filter(|x| x.pair() == &self.pair) - .next() - { + match positions.into_iter().find(|x| x.pair() == &self.pair) { // no open positions for our pair, setting active position to none None => { self.active_position = None; @@ -300,10 +293,7 @@ impl PositionManager { None => self.current_tick() - 1, }; - self.positions_history - .get(&tick) - .filter(|x| x.id() == id) - .and_then(|x| Some(x)) + self.positions_history.get(&tick).filter(|x| x.id() == id) } } @@ -421,13 +411,14 @@ impl OrderManager { if let Some(position) = open_positions.into_iter().find(|x| x.id() == position_id) { let opt_position_order = open_orders .iter() - .find(|x| x.details.amount().neg() == position.amount()); + // avoid using direct equality, using error margin instead + .find(|x| (x.details.amount().neg() - position.amount()).abs() < 0.0000001); // checking if the position has an open order. // If so, don't do anything since the order is taken care of // in the update phase. // If no order is open, send an undercut limit order at the best current price. - if let None = opt_position_order { + if opt_position_order.is_none() { // No open order, undercutting best price with limit order let closing_price = self.best_closing_price(&position, &order_book); @@ -509,26 +500,22 @@ impl OrderManager { let delta = (ask - bid) / 10.0; let closing_price = { - let closing_price = { - if position.is_short() { - bid - delta - } else { - ask + delta - } - }; - - if avg > 9999.0 { - if position.is_short() { - closing_price.ceil() - } else { - closing_price.floor() - } + if position.is_short() { + bid - delta } else { - closing_price + ask + delta } }; - closing_price + if avg > 9999.0 { + if position.is_short() { + closing_price.ceil() + } else { + closing_price.floor() + } + } else { + closing_price + } } } @@ -550,8 +537,8 @@ impl PairManager { Box::new(FastOrderStrategy::default()), ), position_manager: PositionManagerHandle::new( - pair.clone(), - client.clone(), + pair, + client, Box::new(TrailingStop::new()), ), } @@ -578,11 +565,8 @@ impl PairManager { // TODO: to move into Handler? if let Some(messages) = messages { for m in messages { - match m { - Message::ClosePosition { position_id } => { - self.order_manager.close_position(position_id).await?; - } - _ => {} + if let Message::ClosePosition { position_id } = m { + self.order_manager.close_position(position_id).await?; } } } @@ -594,21 +578,19 @@ impl PairManager { pub struct ExchangeManager { kind: ExchangeDetails, pair_managers: Vec, - client: Client, } impl ExchangeManager { - pub fn new(kind: &ExchangeDetails, pairs: &Vec) -> Self { + pub fn new(kind: &ExchangeDetails, pairs: &[SymbolPair]) -> Self { let client = Client::new(kind); let pair_managers = pairs - .into_iter() + .iter() .map(|x| PairManager::new(x.clone(), client.clone())) .collect(); Self { kind: kind.clone(), pair_managers, - client, } } @@ -620,7 +602,7 @@ impl ExchangeManager { .collect(); // execute the futures - while let Some(_) = futures.next().await {} + while futures.next().await.is_some() {} Ok(()) } diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index 2b6d064..8873578 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -139,18 +139,6 @@ impl OrderDetails { } } - pub fn exchange(&self) -> Exchange { - self.exchange - } - pub fn platform(&self) -> TradingPlatform { - self.platform - } - pub fn kind(&self) -> OrderKind { - self.kind - } - pub fn execution_timestamp(&self) -> u64 { - self.execution_timestamp - } pub fn id(&self) -> u64 { self.id } @@ -496,17 +484,6 @@ pub enum PositionProfitState { Profit, } -impl PositionProfitState { - fn color(self) -> String { - match self { - PositionProfitState::Critical | PositionProfitState::Loss => "red", - PositionProfitState::BreakEven => "yellow", - PositionProfitState::MinimumProfit | PositionProfitState::Profit => "green", - } - .into() - } -} - #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub enum PositionState { Closed, diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index af84cd0..374ad62 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -7,11 +7,7 @@ use log::info; use crate::connectors::Connector; use crate::events::{Event, EventKind, EventMetadata, Message}; use crate::managers::OptionUpdate; - -use crate::models::{ - ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionProfitState, - PositionState, TradingFees, TradingPlatform, -}; +use crate::models::{ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PositionProfitState}; use crate::BoxError; /*************** @@ -159,9 +155,9 @@ impl PositionStrategy for TrailingStop { && pl_perc < TrailingStop::GOOD_PROFIT_PERC { PositionProfitState::MinimumProfit - } else if 0.0 <= pl_perc && pl_perc < TrailingStop::MIN_PROFIT_PERC { + } else if (0.0..TrailingStop::MIN_PROFIT_PERC).contains(&pl_perc) { PositionProfitState::BreakEven - } else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 { + } else if (TrailingStop::MAX_LOSS_PERC..0.0).contains(&pl_perc) { PositionProfitState::Loss } else { PositionProfitState::Critical @@ -170,7 +166,7 @@ impl PositionStrategy for TrailingStop { let opt_prev_position = positions_history.get(&(current_tick - 1)); let event_metadata = EventMetadata::new(Some(position.id()), None); - let new_position = position.clone().with_profit_state(Some(state)); + let new_position = position.with_profit_state(Some(state)); match opt_prev_position { Some(prev) => { @@ -219,7 +215,7 @@ impl PositionStrategy for TrailingStop { events }; - return (new_position, Some(events), None); + (new_position, Some(events), None) } fn post_tick( @@ -233,14 +229,9 @@ impl PositionStrategy for TrailingStop { }; // if critical, early return with close position - if let Some(profit_state) = position.profit_state() { - match profit_state { - PositionProfitState::Critical => { - info!("Maximum loss reached. Closing position."); - return (position, None, Some(vec![close_message])); - } - _ => {} - } + if let Some(PositionProfitState::Critical) = position.profit_state() { + info!("Maximum loss reached. Closing position."); + return (position, None, Some(vec![close_message])); }; // let's check if we surpassed an existing stop percentage @@ -270,12 +261,6 @@ impl Default for FastOrderStrategy { } } -impl FastOrderStrategy { - pub fn new(threshold: f64) -> Self { - Self { threshold } - } -} - impl OrderStrategy for FastOrderStrategy { fn name(&self) -> String { "Fast order strategy".into() @@ -310,7 +295,7 @@ impl OrderStrategy for FastOrderStrategy { order: OrderForm::new( order.symbol.clone(), OrderKind::Market {}, - order.details.platform().clone(), + *order.details.platform(), order.details.amount(), ), }) -- 2.47.2 From 70c2dcde172ad6a8a61eecc962a335824ef61b59 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Tue, 16 Feb 2021 18:17:30 +0000 Subject: [PATCH 118/127] added deriv test usdt/btc --- rustybot/src/currency.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rustybot/src/currency.rs b/rustybot/src/currency.rs index 58d4f4c..bed1115 100644 --- a/rustybot/src/currency.rs +++ b/rustybot/src/currency.rs @@ -36,6 +36,9 @@ impl Symbol { pub const TESTBTC: Symbol = Symbol::new_static("TESTBTC"); pub const TESTUSD: Symbol = Symbol::new_static("TESTUSD"); + pub const DERIV_TESTBTC: Symbol = Symbol::new_static("TESTBTCF0"); + pub const DERIV_TESTUSDT: Symbol = Symbol::new_static("TESTUSDTF0"); + // Fiat coins pub const USD: Symbol = Symbol::new_static("USD"); pub const GBP: Symbol = Symbol::new_static("GBP"); -- 2.47.2 From 6e848c35e36d337666f2dcf4b3b34c37e0469d09 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Tue, 16 Feb 2021 18:17:48 +0000 Subject: [PATCH 119/127] early exit on empty positions --- rustybot/src/connectors.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 3d18dcf..596ece5 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -66,6 +66,10 @@ impl Client { let (mut positions, order_book, fees) = (positions?, order_book?, fees?); let (best_ask, best_bid) = (order_book.lowest_ask(), order_book.highest_bid()); + if positions.is_none() { + return Ok(None); + } + let derivative_taker = fees .iter() .filter_map(|x| match x { @@ -345,7 +349,9 @@ impl Connector for BitfinexConnector { // adding leverage, if any match order.leverage() { - Some(leverage) => pre_leverage.with_leverage(leverage as u32), + // TODO: CHANGEME!!!! + Some(leverage) => pre_leverage.with_leverage(15), + // Some(leverage) => pre_leverage.with_leverage(leverage.round() as u32), None => pre_leverage, } }; @@ -417,7 +423,8 @@ impl Connector for BitfinexConnector { async fn trading_fees(&self) -> Result, BoxError> { let mut fees = vec![]; - let accountfees = self.bfx.account.account_summary().await?; + let accountfees = + BitfinexConnector::retry_nonce(|| self.bfx.account.account_summary()).await?; // Derivatives let derivative_taker = TradingFees::Taker { @@ -507,6 +514,7 @@ impl TryInto for bitfinex::positions::Position { } }; + println!("leverage: {}", self.leverage()); Ok(Position::new( SymbolPair::from_str(self.symbol())?, state, -- 2.47.2 From 7f848223b994b2d46163953313908ddf11aed95e Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Tue, 16 Feb 2021 18:18:39 +0000 Subject: [PATCH 120/127] renamed strategies --- rustybot/src/managers.rs | 6 +-- rustybot/src/strategy.rs | 99 ++++++++++++++-------------------------- 2 files changed, 36 insertions(+), 69 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 77fe2c1..ed32db7 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -16,7 +16,7 @@ use crate::events::{ActorMessage, Event, Message}; use crate::models::{ ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker, TradingPlatform, }; -use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop}; +use crate::strategy::{HiddenTrailingStop, MarketEnforce, OrderStrategy, PositionStrategy}; use crate::BoxError; pub type OptionUpdate = (Option>, Option>); @@ -534,12 +534,12 @@ impl PairManager { order_manager: OrderManagerHandle::new( pair.clone(), client.clone(), - Box::new(FastOrderStrategy::default()), + Box::new(MarketEnforce::default()), ), position_manager: PositionManagerHandle::new( pair, client, - Box::new(TrailingStop::new()), + Box::new(HiddenTrailingStop::new()), ), } } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 374ad62..e4fe8da 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -67,15 +67,15 @@ impl Debug for dyn OrderStrategy { ***************/ #[derive(Clone, Debug)] -pub struct TrailingStop { +pub struct HiddenTrailingStop { stop_percentages: HashMap, } -impl TrailingStop { +impl HiddenTrailingStop { // in percentage - const CAPITAL_MAX_LOSS: f64 = 17.5; - const CAPITAL_MIN_PROFIT: f64 = 10.0; - const CAPITAL_GOOD_PROFIT: f64 = 20.0; + const CAPITAL_MAX_LOSS: f64 = 15.0; + const CAPITAL_MIN_PROFIT: f64 = 9.0; + const CAPITAL_GOOD_PROFIT: f64 = HiddenTrailingStop::CAPITAL_MIN_PROFIT * 2.0; // in percentage const MIN_PROFIT_TRAILING_DELTA: f64 = 0.2; @@ -83,14 +83,17 @@ impl TrailingStop { const LEVERAGE: f64 = 15.0; - const MIN_PROFIT_PERC: f64 = (TrailingStop::CAPITAL_MIN_PROFIT / TrailingStop::LEVERAGE) - + TrailingStop::MIN_PROFIT_TRAILING_DELTA; - const GOOD_PROFIT_PERC: f64 = (TrailingStop::CAPITAL_GOOD_PROFIT / TrailingStop::LEVERAGE) - + TrailingStop::GOOD_PROFIT_TRAILING_DELTA; - const MAX_LOSS_PERC: f64 = -(TrailingStop::CAPITAL_MAX_LOSS / TrailingStop::LEVERAGE); + const MIN_PROFIT_PERC: f64 = (HiddenTrailingStop::CAPITAL_MIN_PROFIT + / HiddenTrailingStop::LEVERAGE) + + HiddenTrailingStop::MIN_PROFIT_TRAILING_DELTA; + const GOOD_PROFIT_PERC: f64 = (HiddenTrailingStop::CAPITAL_GOOD_PROFIT + / HiddenTrailingStop::LEVERAGE) + + HiddenTrailingStop::GOOD_PROFIT_TRAILING_DELTA; + const MAX_LOSS_PERC: f64 = + -(HiddenTrailingStop::CAPITAL_MAX_LOSS / HiddenTrailingStop::LEVERAGE); pub fn new() -> Self { - TrailingStop { + HiddenTrailingStop { stop_percentages: HashMap::new(), } } @@ -98,8 +101,10 @@ impl TrailingStop { fn update_stop_percentage(&mut self, position: &Position) { if let Some(profit_state) = position.profit_state() { let profit_state_delta = match profit_state { - PositionProfitState::MinimumProfit => Some(TrailingStop::MIN_PROFIT_TRAILING_DELTA), - PositionProfitState::Profit => Some(TrailingStop::GOOD_PROFIT_TRAILING_DELTA), + PositionProfitState::MinimumProfit => { + Some(HiddenTrailingStop::MIN_PROFIT_TRAILING_DELTA) + } + PositionProfitState::Profit => Some(HiddenTrailingStop::GOOD_PROFIT_TRAILING_DELTA), _ => None, }; @@ -125,8 +130,10 @@ impl TrailingStop { } } info!( - "\tState: {:?} | PL%: {:0.2} | Stop: {:0.2}", + "\tState: {:?} | PL: {:0.2}{} ({:0.2}%) | Stop: {:0.2}", position.profit_state().unwrap(), + position.pl(), + position.pair().quote(), position.pl_perc(), self.stop_percentages.get(&position.id()).unwrap_or(&0.0) ); @@ -134,9 +141,9 @@ impl TrailingStop { } } -impl PositionStrategy for TrailingStop { +impl PositionStrategy for HiddenTrailingStop { fn name(&self) -> String { - "Trailing stop".into() + "Hidden Trailing Stop".into() } /// Sets the profit state of an open position @@ -149,15 +156,15 @@ impl PositionStrategy for TrailingStop { let pl_perc = position.pl_perc(); let state = { - if pl_perc > TrailingStop::GOOD_PROFIT_PERC { + if pl_perc > HiddenTrailingStop::GOOD_PROFIT_PERC { PositionProfitState::Profit - } else if TrailingStop::MIN_PROFIT_PERC <= pl_perc - && pl_perc < TrailingStop::GOOD_PROFIT_PERC + } else if HiddenTrailingStop::MIN_PROFIT_PERC <= pl_perc + && pl_perc < HiddenTrailingStop::GOOD_PROFIT_PERC { PositionProfitState::MinimumProfit - } else if (0.0..TrailingStop::MIN_PROFIT_PERC).contains(&pl_perc) { + } else if (0.0..HiddenTrailingStop::MIN_PROFIT_PERC).contains(&pl_perc) { PositionProfitState::BreakEven - } else if (TrailingStop::MAX_LOSS_PERC..0.0).contains(&pl_perc) { + } else if (HiddenTrailingStop::MAX_LOSS_PERC..0.0).contains(&pl_perc) { PositionProfitState::Loss } else { PositionProfitState::Critical @@ -249,21 +256,21 @@ impl PositionStrategy for TrailingStop { } #[derive(Clone, Debug)] -pub struct FastOrderStrategy { +pub struct MarketEnforce { // threshold (%) for which we trigger a market order // to close an open position threshold: f64, } -impl Default for FastOrderStrategy { +impl Default for MarketEnforce { fn default() -> Self { Self { threshold: 0.15 } } } -impl OrderStrategy for FastOrderStrategy { +impl OrderStrategy for MarketEnforce { fn name(&self) -> String { - "Fast order strategy".into() + "Market Enforce".into() } fn on_open_order( @@ -294,7 +301,7 @@ impl OrderStrategy for FastOrderStrategy { messages.push(Message::SubmitOrder { order: OrderForm::new( order.symbol.clone(), - OrderKind::Market {}, + OrderKind::Market, *order.details.platform(), order.details.amount(), ), @@ -303,44 +310,4 @@ impl OrderStrategy for FastOrderStrategy { Ok((None, (!messages.is_empty()).then_some(messages))) } - - // fn on_position_order( - // &self, - // order: &ActiveOrder, - // _: &Position, - // order_book: &OrderBook, - // ) -> Result { - // let mut messages = vec![]; - // - // // long - // let offer_comparison = { - // if order.current_form.amount() > 0.0 { - // order_book.highest_bid() - // } else { - // order_book.lowest_ask() - // } - // }; - // - // // if the best offer is higher than our threshold, - // // ask the manager to close the position with a market order - // let order_price = order - // .current_form - // .price() - // .ok_or("The active order does not have a price!")?; - // let delta = (1.0 - (offer_comparison / order_price)).abs() * 100.0; - // - // if delta > self.threshold { - // messages.push(Message::SubmitOrder { - // order: OrderForm::new( - // order.symbol.clone(), - // OrderKind::Market { - // amount: order.current_form.amount(), - // }, - // order.current_form.platform().clone(), - // ), - // }) - // } - // - // Ok((None, (!messages.is_empty()).then_some(messages))) - // } } -- 2.47.2 From 762485db3a17c450865cf3656db23c5ff36d9f2c Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 17 Feb 2021 15:57:52 +0000 Subject: [PATCH 121/127] renamed Message into ActionMessage --- rustybot/src/events.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index 1fc24cd..a0f370c 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -5,15 +5,16 @@ use crate::models::OrderForm; #[derive(Debug)] pub struct ActorMessage { - pub(crate) message: Message, + pub(crate) message: ActionMessage, pub(crate) respond_to: oneshot::Sender, } #[derive(Debug)] -pub enum Message { +pub enum ActionMessage { Update { tick: u64 }, ClosePosition { position_id: u64 }, SubmitOrder { order: OrderForm }, + ClosePositionOrders { position_id: u64 }, } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -- 2.47.2 From 53fb8781b3f3fb30aa6d5453cf8615161c8acb1d Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 17 Feb 2021 15:59:32 +0000 Subject: [PATCH 122/127] added trailingstop strategy (useless) but modified hiddentrailing stop: implemented default for both strategies. additionally, the order manager now tries to pair active orders with active positions. order managers now have more APIs such as close orders associated with positions and submit order --- rustybot/src/managers.rs | 169 +++++++++++++++-- rustybot/src/models.rs | 28 +++ rustybot/src/strategy.rs | 389 +++++++++++++++++++++++++++++++++------ 3 files changed, 511 insertions(+), 75 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index ed32db7..0d828c0 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -12,14 +12,14 @@ use tokio::time::Duration; use crate::connectors::{Client, ExchangeDetails}; use crate::currency::SymbolPair; -use crate::events::{ActorMessage, Event, Message}; +use crate::events::{ActionMessage, ActorMessage, Event}; use crate::models::{ ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker, TradingPlatform, }; use crate::strategy::{HiddenTrailingStop, MarketEnforce, OrderStrategy, PositionStrategy}; use crate::BoxError; -pub type OptionUpdate = (Option>, Option>); +pub type OptionUpdate = (Option>, Option>); /****************** * PRICES @@ -44,7 +44,7 @@ impl PriceManager { } pub async fn handle_message(&mut self, message: ActorMessage) -> Result<(), BoxError> { - if let Message::Update { tick } = message.message { + if let ActionMessage::Update { tick } = message.message { let a = self.update(tick).await?; self.add_entry(a); } @@ -100,7 +100,7 @@ impl PriceManagerHandle { self.sender .send(ActorMessage { - message: Message::Update { tick }, + message: ActionMessage::Update { tick }, respond_to: send, }) .await?; @@ -176,7 +176,7 @@ impl PositionManagerHandle { self.sender .send(ActorMessage { - message: Message::Update { tick }, + message: ActionMessage::Update { tick }, respond_to: send, }) .await?; @@ -221,7 +221,7 @@ impl PositionManager { pub async fn handle_message(&mut self, msg: ActorMessage) -> Result<(), BoxError> { let (events, messages) = match msg.message { - Message::Update { tick } => self.update(tick).await?, + ActionMessage::Update { tick } => self.update(tick).await?, _ => (None, None), }; @@ -302,7 +302,7 @@ impl PositionManager { ******************/ // Position ID: Order ID -pub type TrackedPositionsMap = HashMap; +pub type TrackedPositionsMap = HashMap>; pub struct OrderManagerHandle { sender: Sender, @@ -344,7 +344,36 @@ impl OrderManagerHandle { self.sender .send(ActorMessage { - message: Message::ClosePosition { position_id }, + message: ActionMessage::ClosePosition { position_id }, + respond_to: send, + }) + .await?; + + Ok(recv.await?) + } + + pub async fn close_position_orders( + &mut self, + position_id: u64, + ) -> Result { + let (send, recv) = oneshot::channel(); + + self.sender + .send(ActorMessage { + message: ActionMessage::ClosePositionOrders { position_id }, + respond_to: send, + }) + .await?; + + Ok(recv.await?) + } + + pub async fn submit_order(&mut self, order_form: OrderForm) -> Result { + let (send, recv) = oneshot::channel(); + + self.sender + .send(ActorMessage { + message: ActionMessage::SubmitOrder { order: order_form }, respond_to: send, }) .await?; @@ -381,9 +410,14 @@ impl OrderManager { pub async fn handle_message(&mut self, msg: ActorMessage) -> Result<(), BoxError> { let (events, messages) = match msg.message { - Message::Update { .. } => self.update().await?, - Message::ClosePosition { position_id } => self.close_position(position_id).await?, - _ => (None, None), + ActionMessage::Update { .. } => self.update().await?, + ActionMessage::ClosePosition { position_id } => { + self.close_position(position_id).await? + } + ActionMessage::ClosePositionOrders { position_id } => { + self.close_position_orders(position_id).await? + } + ActionMessage::SubmitOrder { order } => self.submit_order(&order).await?, }; Ok(msg @@ -392,6 +426,53 @@ impl OrderManager { .map_err(|_| BoxError::from("Could not send message."))?) } + pub async fn close_position_orders(&self, position_id: u64) -> Result { + info!("Closing outstanding orders for position #{}", position_id); + + if let Some(position_orders) = self.tracked_positions.get(&position_id) { + // retrieving open orders + let open_orders = self.client.active_orders(&self.pair).await?; + let position_orders: Vec<_> = position_orders + .iter() + .filter_map(|&x| open_orders.iter().find(|y| y.id == x)) + .collect(); + + for order in position_orders { + match self.client.cancel_order(order).await { + Ok(_) => info!("Order #{} closed successfully.", order.id), + Err(e) => error!("Could not close order #{}: {}", order.id, e), + } + } + } + + // TODO: return valid messages and events! + Ok((None, None)) + } + + pub async fn submit_order(&mut self, order_form: &OrderForm) -> Result { + info!("Submiting {}", order_form.kind()); + + let active_order = self.client.submit_order(order_form).await?; + + debug!("Adding order to tracked orders."); + if let Some(metadata) = order_form.metadata() { + if let Some(position_id) = metadata.position_id() { + match self.tracked_positions.get_mut(&position_id) { + None => { + self.tracked_positions + .insert(position_id, vec![active_order.id]); + } + Some(position_orders) => { + position_orders.push(active_order.id); + } + } + } + }; + + // TODO: return valid messages and events!111!!!1! + Ok((None, None)) + } + pub async fn close_position(&mut self, position_id: u64) -> Result { info!("Closing position #{}", position_id); @@ -450,8 +531,8 @@ impl OrderManager { Ok((None, None)) } - pub async fn update(&self) -> Result { - trace!("\t[OrderManager] Updating {}", self.pair); + pub async fn update(&mut self) -> Result { + debug!("\t[OrderManager] Updating {}", self.pair); let (res_open_orders, res_order_book) = tokio::join!( self.client.active_orders(&self.pair), @@ -460,8 +541,49 @@ impl OrderManager { let (open_orders, order_book) = (res_open_orders?, res_order_book?); + // retrieving open positions to check whether the positions have open orders. + // we need to update our internal mapping in that case. + if !open_orders.is_empty() { + let open_positions = self.client.active_positions(&self.pair).await?; + + if let Some(positions) = open_positions { + // currently, we are only trying to match orders with an amount equal to + // a position amount. + for position in positions { + let matching_order = open_orders + .iter() + .find(|x| x.details.amount().abs() == position.amount().abs()); + + // if an order is found, we insert the order to our internal mapping, if not already present + if let Some(matching_order) = matching_order { + match self.tracked_positions.get_mut(&position.id()) { + Some(position_orders) => { + if !position_orders.contains(&matching_order.id) { + trace!( + "Mapped order #{} to position #{}", + position.id(), + matching_order.id + ); + position_orders.push(matching_order.id); + } + } + None => { + trace!( + "Mapped order #{} to position #{}", + position.id(), + matching_order.id + ); + self.tracked_positions + .insert(position.id(), vec![matching_order.id]); + } + } + } + } + } + } + for active_order in open_orders { - debug!( + trace!( "Found open order, calling \"{}\" strategy.", self.strategy.name() ); @@ -471,7 +593,7 @@ impl OrderManager { if let Some(messages) = strat_messages { for m in messages { match m { - Message::SubmitOrder { order: order_form } => { + ActionMessage::SubmitOrder { order: order_form } => { info!("Closing open order..."); info!("\tCancelling open order #{}", &active_order.id); self.client.cancel_order(&active_order).await?; @@ -539,7 +661,7 @@ impl PairManager { position_manager: PositionManagerHandle::new( pair, client, - Box::new(HiddenTrailingStop::new()), + Box::new(HiddenTrailingStop::default()), ), } } @@ -565,8 +687,19 @@ impl PairManager { // TODO: to move into Handler? if let Some(messages) = messages { for m in messages { - if let Message::ClosePosition { position_id } = m { - self.order_manager.close_position(position_id).await?; + match m { + ActionMessage::Update { .. } => {} + ActionMessage::ClosePosition { position_id } => { + self.order_manager.close_position(position_id).await?; + } + ActionMessage::SubmitOrder { order } => { + self.order_manager.submit_order(order).await?; + } + ActionMessage::ClosePositionOrders { position_id } => { + self.order_manager + .close_position_orders(position_id) + .await?; + } } } } diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index 8873578..01fa2f6 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -265,6 +265,7 @@ pub struct OrderForm { platform: TradingPlatform, amount: f64, leverage: Option, + metadata: Option, } impl OrderForm { @@ -280,6 +281,7 @@ impl OrderForm { platform, amount, leverage: None, + metadata: None, } } @@ -288,6 +290,11 @@ impl OrderForm { self } + pub fn with_metadata(mut self, metadata: OrderMetadata) -> Self { + self.metadata = Some(metadata); + self + } + pub fn pair(&self) -> &SymbolPair { &self.pair } @@ -319,6 +326,27 @@ impl OrderForm { pub fn leverage(&self) -> Option { self.leverage } + + pub fn metadata(&self) -> &Option { + &self.metadata + } +} + +#[derive(Debug, Clone)] +pub struct OrderMetadata { + position_id: Option, +} + +impl OrderMetadata { + pub fn with_position_id(position_id: u64) -> Self { + OrderMetadata { + position_id: Some(position_id), + } + } + + pub fn position_id(&self) -> Option { + self.position_id + } } /*************** diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index e4fe8da..23d4ecd 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -1,13 +1,16 @@ use std::collections::HashMap; use std::fmt::{Debug, Formatter}; + use dyn_clone::DynClone; -use log::info; +use log::{info}; use crate::connectors::Connector; -use crate::events::{Event, EventKind, EventMetadata, Message}; +use crate::events::{ActionMessage, Event, EventKind, EventMetadata}; use crate::managers::OptionUpdate; -use crate::models::{ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PositionProfitState}; +use crate::models::{ + ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PositionProfitState, +}; use crate::BoxError; /*************** @@ -21,13 +24,13 @@ pub trait PositionStrategy: DynClone + Send + Sync { position: Position, current_tick: u64, positions_history: &HashMap, - ) -> (Position, Option>, Option>); + ) -> (Position, Option>, Option>); fn post_tick( &mut self, position: Position, current_tick: u64, positions_history: &HashMap, - ) -> (Position, Option>, Option>); + ) -> (Position, Option>, Option>); } impl Debug for dyn PositionStrategy { @@ -69,66 +72,47 @@ impl Debug for dyn OrderStrategy { #[derive(Clone, Debug)] pub struct HiddenTrailingStop { stop_percentages: HashMap, + capital_max_loss: f64, + capital_min_profit: f64, + capital_good_profit: f64, + min_profit_trailing_delta: f64, + good_profit_trailing_delta: f64, + leverage: f64, + min_profit_percentage: f64, + good_profit_percentage: f64, + max_loss_percentage: f64, } impl HiddenTrailingStop { - // in percentage - const CAPITAL_MAX_LOSS: f64 = 15.0; - const CAPITAL_MIN_PROFIT: f64 = 9.0; - const CAPITAL_GOOD_PROFIT: f64 = HiddenTrailingStop::CAPITAL_MIN_PROFIT * 2.0; - - // in percentage - const MIN_PROFIT_TRAILING_DELTA: f64 = 0.2; - const GOOD_PROFIT_TRAILING_DELTA: f64 = 0.1; - - const LEVERAGE: f64 = 15.0; - - const MIN_PROFIT_PERC: f64 = (HiddenTrailingStop::CAPITAL_MIN_PROFIT - / HiddenTrailingStop::LEVERAGE) - + HiddenTrailingStop::MIN_PROFIT_TRAILING_DELTA; - const GOOD_PROFIT_PERC: f64 = (HiddenTrailingStop::CAPITAL_GOOD_PROFIT - / HiddenTrailingStop::LEVERAGE) - + HiddenTrailingStop::GOOD_PROFIT_TRAILING_DELTA; - const MAX_LOSS_PERC: f64 = - -(HiddenTrailingStop::CAPITAL_MAX_LOSS / HiddenTrailingStop::LEVERAGE); - - pub fn new() -> Self { - HiddenTrailingStop { - stop_percentages: HashMap::new(), - } - } - fn update_stop_percentage(&mut self, position: &Position) { if let Some(profit_state) = position.profit_state() { let profit_state_delta = match profit_state { - PositionProfitState::MinimumProfit => { - Some(HiddenTrailingStop::MIN_PROFIT_TRAILING_DELTA) - } - PositionProfitState::Profit => Some(HiddenTrailingStop::GOOD_PROFIT_TRAILING_DELTA), + PositionProfitState::MinimumProfit => Some(self.min_profit_trailing_delta), + PositionProfitState::Profit => Some(self.good_profit_trailing_delta), _ => None, }; if let Some(profit_state_delta) = profit_state_delta { let current_stop_percentage = position.pl_perc() - profit_state_delta; - match profit_state { - PositionProfitState::MinimumProfit | PositionProfitState::Profit => { - match self.stop_percentages.get(&position.id()) { - None => { + if let PositionProfitState::MinimumProfit | PositionProfitState::Profit = + profit_state + { + match self.stop_percentages.get(&position.id()) { + None => { + self.stop_percentages + .insert(position.id(), current_stop_percentage); + } + Some(existing_threshold) => { + if existing_threshold < ¤t_stop_percentage { self.stop_percentages .insert(position.id(), current_stop_percentage); } - Some(existing_threshold) => { - if existing_threshold < ¤t_stop_percentage { - self.stop_percentages - .insert(position.id(), current_stop_percentage); - } - } } } - _ => {} } } + info!( "\tState: {:?} | PL: {:0.2}{} ({:0.2}%) | Stop: {:0.2}", position.profit_state().unwrap(), @@ -141,6 +125,40 @@ impl HiddenTrailingStop { } } +impl Default for HiddenTrailingStop { + fn default() -> Self { + let leverage = 5.0; + + // in percentage + let capital_max_loss = 15.0; + let capital_min_profit = 9.0; + let capital_good_profit = capital_min_profit * 2.0; + + let weighted_min_profit = capital_min_profit / leverage; + let weighted_good_profit = capital_good_profit / leverage; + let weighted_max_loss = capital_max_loss / leverage; + + let min_profit_trailing_delta = weighted_min_profit * 0.17; + let good_profit_trailing_delta = weighted_good_profit * 0.08; + + let min_profit_percentage = weighted_min_profit + min_profit_trailing_delta; + let good_profit_percentage = weighted_good_profit + good_profit_trailing_delta; + let max_loss_percentage = -weighted_max_loss; + + HiddenTrailingStop { + stop_percentages: Default::default(), + capital_max_loss, + capital_min_profit, + capital_good_profit, + min_profit_trailing_delta, + good_profit_trailing_delta, + leverage, + min_profit_percentage, + good_profit_percentage, + max_loss_percentage, + } + } +} impl PositionStrategy for HiddenTrailingStop { fn name(&self) -> String { "Hidden Trailing Stop".into() @@ -152,19 +170,17 @@ impl PositionStrategy for HiddenTrailingStop { position: Position, current_tick: u64, positions_history: &HashMap, - ) -> (Position, Option>, Option>) { + ) -> (Position, Option>, Option>) { let pl_perc = position.pl_perc(); let state = { - if pl_perc > HiddenTrailingStop::GOOD_PROFIT_PERC { + if pl_perc > self.good_profit_percentage { PositionProfitState::Profit - } else if HiddenTrailingStop::MIN_PROFIT_PERC <= pl_perc - && pl_perc < HiddenTrailingStop::GOOD_PROFIT_PERC - { + } else if (self.min_profit_percentage..self.good_profit_percentage).contains(&pl_perc) { PositionProfitState::MinimumProfit - } else if (0.0..HiddenTrailingStop::MIN_PROFIT_PERC).contains(&pl_perc) { + } else if (0.0..self.min_profit_percentage).contains(&pl_perc) { PositionProfitState::BreakEven - } else if (HiddenTrailingStop::MAX_LOSS_PERC..0.0).contains(&pl_perc) { + } else if (self.max_loss_percentage..0.0).contains(&pl_perc) { PositionProfitState::Loss } else { PositionProfitState::Critical @@ -230,8 +246,8 @@ impl PositionStrategy for HiddenTrailingStop { position: Position, _: u64, _: &HashMap, - ) -> (Position, Option>, Option>) { - let close_message = Message::ClosePosition { + ) -> (Position, Option>, Option>) { + let close_message = ActionMessage::ClosePosition { position_id: position.id(), }; @@ -255,6 +271,263 @@ impl PositionStrategy for HiddenTrailingStop { } } +// #[derive(Clone, Debug)] +// pub struct TrailingStop { +// stop_percentages: HashMap, +// capital_max_loss: f64, +// capital_min_profit: f64, +// capital_good_profit: f64, +// min_profit_trailing_delta: f64, +// good_profit_trailing_delta: f64, +// leverage: f64, +// min_profit_percentage: f64, +// good_profit_percentage: f64, +// max_loss_percentage: f64, +// } +// +// impl TrailingStop { +// fn update_stop_percentage(&mut self, position: &Position) -> Option { +// let mut order_form = None; +// +// if let Some(profit_state) = position.profit_state() { +// let profit_state_delta = match profit_state { +// PositionProfitState::MinimumProfit => Some(self.min_profit_trailing_delta), +// PositionProfitState::Profit => Some(self.good_profit_trailing_delta), +// _ => None, +// }; +// +// if let Some(profit_state_delta) = profit_state_delta { +// let current_stop_percentage = position.pl_perc() - profit_state_delta; +// let price_percentage_delta = { +// if position.is_short() { +// // 1.0 is the base price +// 1.0 - current_stop_percentage / 100.0 +// } else { +// 1.0 + current_stop_percentage / 100.0 +// } +// }; +// +// println!("Delta: {}", price_percentage_delta); +// +// if let PositionProfitState::MinimumProfit | PositionProfitState::Profit = +// profit_state +// { +// match self.stop_percentages.get(&position.id()) { +// None => { +// self.stop_percentages +// .insert(position.id(), current_stop_percentage); +// +// trace!("Setting trailing stop, asking order manager to cancel previous orders."); +// order_form = Some( +// OrderForm::new( +// position.pair().clone(), +// OrderKind::Limit { +// price: position.base_price() * price_percentage_delta, +// }, +// position.platform(), +// position.amount().neg(), +// ) +// .with_metadata(OrderMetadata::with_position_id(position.id())), +// ); +// } +// Some(existing_threshold) => { +// // follow and update trailing stop +// if existing_threshold < ¤t_stop_percentage { +// self.stop_percentages +// .insert(position.id(), current_stop_percentage); +// +// trace!("Updating threshold, asking order manager to cancel previous orders."); +// order_form = Some( +// OrderForm::new( +// position.pair().clone(), +// OrderKind::Limit { +// price: position.base_price() * price_percentage_delta, +// }, +// position.platform(), +// position.amount().neg(), +// ) +// .with_metadata(OrderMetadata::with_position_id(position.id())), +// ); +// } +// } +// } +// } +// } +// +// info!( +// "\tState: {:?} | PL: {:0.2}{} ({:0.2}%) | Stop: {:0.2}", +// position.profit_state().unwrap(), +// position.pl(), +// position.pair().quote(), +// position.pl_perc(), +// self.stop_percentages.get(&position.id()).unwrap_or(&0.0) +// ); +// } +// +// order_form +// } +// } +// +// impl Default for TrailingStop { +// fn default() -> Self { +// let leverage = 5.0; +// +// // in percentage +// let capital_max_loss = 15.0; +// let capital_min_profit = 1.0; +// let capital_good_profit = 6.0; +// +// let weighted_min_profit = capital_min_profit / leverage; +// let weighted_good_profit = capital_good_profit / leverage; +// let weighted_max_loss = capital_max_loss / leverage; +// +// let min_profit_trailing_delta = weighted_min_profit * 0.2; +// let good_profit_trailing_delta = weighted_good_profit * 0.08; +// +// let min_profit_percentage = weighted_min_profit + min_profit_trailing_delta; +// let good_profit_percentage = weighted_good_profit + good_profit_trailing_delta; +// let max_loss_percentage = -weighted_max_loss; +// +// TrailingStop { +// stop_percentages: Default::default(), +// capital_max_loss, +// capital_min_profit, +// capital_good_profit, +// min_profit_trailing_delta, +// good_profit_trailing_delta, +// leverage, +// min_profit_percentage, +// good_profit_percentage, +// max_loss_percentage, +// } +// } +// } +// impl PositionStrategy for TrailingStop { +// fn name(&self) -> String { +// "Trailing Stop".into() +// } +// +// /// Sets the profit state of an open position +// fn on_tick( +// &mut self, +// position: Position, +// current_tick: u64, +// positions_history: &HashMap, +// ) -> (Position, Option>, Option>) { +// let pl_perc = position.pl_perc(); +// +// let state = { +// if pl_perc > self.good_profit_percentage { +// PositionProfitState::Profit +// } else if (self.min_profit_percentage..self.good_profit_percentage).contains(&pl_perc) { +// PositionProfitState::MinimumProfit +// } else if (0.0..self.min_profit_percentage).contains(&pl_perc) { +// PositionProfitState::BreakEven +// } else if (self.max_loss_percentage..0.0).contains(&pl_perc) { +// PositionProfitState::Loss +// } else { +// PositionProfitState::Critical +// } +// }; +// +// let opt_prev_position = positions_history.get(&(current_tick - 1)); +// let event_metadata = EventMetadata::new(Some(position.id()), None); +// let new_position = position.with_profit_state(Some(state)); +// +// match opt_prev_position { +// Some(prev) => { +// if prev.profit_state() == Some(state) { +// return (new_position, None, None); +// } +// } +// None => return (new_position, None, None), +// }; +// +// let events = { +// let mut events = vec![]; +// +// if state == PositionProfitState::Profit { +// events.push(Event::new( +// EventKind::ReachedGoodProfit, +// current_tick, +// Some(event_metadata), +// )); +// } else if state == PositionProfitState::MinimumProfit { +// events.push(Event::new( +// EventKind::ReachedMinProfit, +// current_tick, +// Some(event_metadata), +// )); +// } else if state == PositionProfitState::BreakEven { +// events.push(Event::new( +// EventKind::ReachedBreakEven, +// current_tick, +// Some(event_metadata), +// )); +// } else if state == PositionProfitState::Loss { +// events.push(Event::new( +// EventKind::ReachedLoss, +// current_tick, +// Some(event_metadata), +// )); +// } else { +// events.push(Event::new( +// EventKind::ReachedMaxLoss, +// current_tick, +// Some(event_metadata), +// )); +// } +// +// events +// }; +// +// (new_position, Some(events), None) +// } +// +// fn post_tick( +// &mut self, +// position: Position, +// _: u64, +// _: &HashMap, +// ) -> (Position, Option>, Option>) { +// let close_message = ActionMessage::ClosePosition { +// position_id: position.id(), +// }; +// +// // if critical, early return with close position +// if let Some(PositionProfitState::Critical) = position.profit_state() { +// info!("Maximum loss reached. Closing position."); +// return (position, None, Some(vec![close_message])); +// }; +// +// // let's check if we surpassed an existing stop percentage +// if let Some(existing_stop_percentage) = self.stop_percentages.get(&position.id()) { +// if &position.pl_perc() <= existing_stop_percentage { +// info!("Stop percentage surpassed. Closing position."); +// return (position, None, Some(vec![close_message])); +// } +// } +// +// // updated or new trailing stop. should cancel orders and submit new one +// if let Some(order_form) = self.update_stop_percentage(&position) { +// let mut messages = vec![]; +// +// messages.push(ActionMessage::ClosePositionOrders { +// position_id: position.id(), +// }); +// messages.push(ActionMessage::SubmitOrder { order: order_form }); +// +// return (position, None, Some(messages)); +// } +// +// (position, None, None) +// } +// } + +/* + * ORDER STRATEGIES + */ + #[derive(Clone, Debug)] pub struct MarketEnforce { // threshold (%) for which we trigger a market order @@ -264,7 +537,9 @@ pub struct MarketEnforce { impl Default for MarketEnforce { fn default() -> Self { - Self { threshold: 0.15 } + Self { + threshold: 1.0 / 15.0, + } } } @@ -298,7 +573,7 @@ impl OrderStrategy for MarketEnforce { let delta = (1.0 - (offer_comparison / order_price)).abs() * 100.0; if delta > self.threshold { - messages.push(Message::SubmitOrder { + messages.push(ActionMessage::SubmitOrder { order: OrderForm::new( order.symbol.clone(), OrderKind::Market, -- 2.47.2 From c7c4dd59024b452e22c3f0a50798faf1df0c8b7b Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 17 Feb 2021 16:23:34 +0000 Subject: [PATCH 123/127] changed visibility of ActiveOrder's fields. implemented getters --- rustybot/src/connectors.rs | 53 +++++++++++++-------------- rustybot/src/managers.rs | 28 ++++++++------- rustybot/src/models.rs | 73 +++++++++++++++++++++++++++++++++----- rustybot/src/strategy.rs | 17 ++++----- 4 files changed, 112 insertions(+), 59 deletions(-) diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 596ece5..5b84ee7 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -123,7 +123,7 @@ impl Client { .active_orders(pair) .await? .into_iter() - .filter(|x| &x.symbol == pair) + .filter(|x| &x.pair() == &pair) .collect()) } @@ -350,7 +350,7 @@ impl Connector for BitfinexConnector { // adding leverage, if any match order.leverage() { // TODO: CHANGEME!!!! - Some(leverage) => pre_leverage.with_leverage(15), + Some(_leverage) => pre_leverage.with_leverage(15), // Some(leverage) => pre_leverage.with_leverage(leverage.round() as u32), None => pre_leverage, } @@ -468,7 +468,7 @@ impl Connector for BitfinexConnector { impl From<&ActiveOrder> for CancelOrderForm { fn from(o: &ActiveOrder) -> Self { - Self::from_id(o.id) + Self::from_id(o.id()) } } @@ -476,21 +476,18 @@ impl TryFrom<&bitfinex::responses::OrderResponse> for ActiveOrder { type Error = BoxError; fn try_from(response: &OrderResponse) -> Result { - Ok(Self { - exchange: Exchange::Bitfinex, - id: response.id(), - group_id: response.gid(), - client_id: Some(response.cid()), - symbol: SymbolPair::from_str(response.symbol())?, - details: OrderForm::new( - SymbolPair::from_str(response.symbol())?, - response.into(), - response.into(), - response.amount(), - ), - creation_timestamp: 0, - update_timestamp: 0, - }) + let pair = SymbolPair::from_str(response.symbol())?; + + Ok(ActiveOrder::new( + Exchange::Bitfinex, + response.id(), + pair.clone(), + OrderForm::new(pair, response.into(), response.into(), response.amount()), + response.mts_create(), + response.mts_update(), + ) + .with_group_id(response.gid()) + .with_client_id(Some(response.cid()))) } } @@ -670,16 +667,16 @@ impl From<&bitfinex::orders::ActiveOrder> for ActiveOrder { fn from(order: &bitfinex::orders::ActiveOrder) -> Self { let pair = SymbolPair::from_str(&order.symbol()).expect("Invalid symbol!"); - Self { - exchange: Exchange::Bitfinex, - id: order.id(), - group_id: order.group_id().map(|x| x as u64), - client_id: Some(order.client_id()), - symbol: pair.clone(), - details: OrderForm::new(pair, order.into(), order.into(), order.amount()), - creation_timestamp: order.creation_timestamp(), - update_timestamp: order.update_timestamp(), - } + ActiveOrder::new( + Exchange::Bitfinex, + order.id(), + pair.clone(), + OrderForm::new(pair, order.into(), order.into(), order.amount()), + order.creation_timestamp(), + order.update_timestamp(), + ) + .with_client_id(Some(order.client_id())) + .with_group_id(order.group_id()) } } diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index 0d828c0..ebc04ba 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -434,13 +434,13 @@ impl OrderManager { let open_orders = self.client.active_orders(&self.pair).await?; let position_orders: Vec<_> = position_orders .iter() - .filter_map(|&x| open_orders.iter().find(|y| y.id == x)) + .filter_map(|&x| open_orders.iter().find(|y| y.id() == x)) .collect(); for order in position_orders { match self.client.cancel_order(order).await { - Ok(_) => info!("Order #{} closed successfully.", order.id), - Err(e) => error!("Could not close order #{}: {}", order.id, e), + Ok(_) => info!("Order #{} closed successfully.", order.id()), + Err(e) => error!("Could not close order #{}: {}", order.id(), e), } } } @@ -460,10 +460,10 @@ impl OrderManager { match self.tracked_positions.get_mut(&position_id) { None => { self.tracked_positions - .insert(position_id, vec![active_order.id]); + .insert(position_id, vec![active_order.id()]); } Some(position_orders) => { - position_orders.push(active_order.id); + position_orders.push(active_order.id()); } } } @@ -493,7 +493,9 @@ impl OrderManager { let opt_position_order = open_orders .iter() // avoid using direct equality, using error margin instead - .find(|x| (x.details.amount().neg() - position.amount()).abs() < 0.0000001); + .find(|x| { + (x.order_form().amount().neg() - position.amount()).abs() < 0.0000001 + }); // checking if the position has an open order. // If so, don't do anything since the order is taken care of @@ -552,29 +554,29 @@ impl OrderManager { for position in positions { let matching_order = open_orders .iter() - .find(|x| x.details.amount().abs() == position.amount().abs()); + .find(|x| x.order_form().amount().abs() == position.amount().abs()); // if an order is found, we insert the order to our internal mapping, if not already present if let Some(matching_order) = matching_order { match self.tracked_positions.get_mut(&position.id()) { Some(position_orders) => { - if !position_orders.contains(&matching_order.id) { + if !position_orders.contains(&matching_order.id()) { trace!( "Mapped order #{} to position #{}", position.id(), - matching_order.id + matching_order.id() ); - position_orders.push(matching_order.id); + position_orders.push(matching_order.id()); } } None => { trace!( "Mapped order #{} to position #{}", position.id(), - matching_order.id + matching_order.id() ); self.tracked_positions - .insert(position.id(), vec![matching_order.id]); + .insert(position.id(), vec![matching_order.id()]); } } } @@ -595,7 +597,7 @@ impl OrderManager { match m { ActionMessage::SubmitOrder { order: order_form } => { info!("Closing open order..."); - info!("\tCancelling open order #{}", &active_order.id); + info!("\tCancelling open order #{}", &active_order.id()); self.client.cancel_order(&active_order).await?; info!("\tSubmitting {}...", order_form.kind()); diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index 01fa2f6..9e8cc7c 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -150,14 +150,71 @@ impl OrderDetails { #[derive(Clone, Debug)] pub struct ActiveOrder { - pub(crate) exchange: Exchange, - pub(crate) id: u64, - pub(crate) group_id: Option, - pub(crate) client_id: Option, - pub(crate) symbol: SymbolPair, - pub(crate) details: OrderForm, - pub(crate) creation_timestamp: u64, - pub(crate) update_timestamp: u64, + exchange: Exchange, + id: u64, + group_id: Option, + client_id: Option, + pair: SymbolPair, + order_form: OrderForm, + creation_timestamp: u64, + update_timestamp: u64, +} + +impl ActiveOrder { + pub fn new( + exchange: Exchange, + id: u64, + pair: SymbolPair, + order_form: OrderForm, + creation_timestamp: u64, + update_timestamp: u64, + ) -> Self { + Self { + exchange, + id, + group_id: None, + client_id: None, + pair, + order_form, + creation_timestamp, + update_timestamp, + } + } + + pub fn with_group_id(mut self, group_id: Option) -> Self { + self.group_id = group_id; + self + } + + pub fn with_client_id(mut self, client_id: Option) -> Self { + self.client_id = client_id; + self + } + + pub fn exchange(&self) -> Exchange { + self.exchange + } + pub fn id(&self) -> u64 { + self.id + } + pub fn group_id(&self) -> Option { + self.group_id + } + pub fn client_id(&self) -> Option { + self.client_id + } + pub fn pair(&self) -> &SymbolPair { + &self.pair + } + pub fn order_form(&self) -> &OrderForm { + &self.order_form + } + pub fn creation_timestamp(&self) -> u64 { + self.creation_timestamp + } + pub fn update_timestamp(&self) -> u64 { + self.update_timestamp + } } impl Hash for ActiveOrder { diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 23d4ecd..2503ba0 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -1,16 +1,13 @@ use std::collections::HashMap; use std::fmt::{Debug, Formatter}; - use dyn_clone::DynClone; -use log::{info}; +use log::info; use crate::connectors::Connector; use crate::events::{ActionMessage, Event, EventKind, EventMetadata}; use crate::managers::OptionUpdate; -use crate::models::{ - ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PositionProfitState, -}; +use crate::models::{ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PositionProfitState}; use crate::BoxError; /*************** @@ -557,7 +554,7 @@ impl OrderStrategy for MarketEnforce { // long let offer_comparison = { - if order.details.amount() > 0.0 { + if order.order_form().amount() > 0.0 { order_book.highest_bid() } else { order_book.lowest_ask() @@ -567,7 +564,7 @@ impl OrderStrategy for MarketEnforce { // if the best offer is higher than our threshold, // ask the manager to close the position with a market order let order_price = order - .details + .order_form() .price() .ok_or("The active order does not have a price!")?; let delta = (1.0 - (offer_comparison / order_price)).abs() * 100.0; @@ -575,10 +572,10 @@ impl OrderStrategy for MarketEnforce { if delta > self.threshold { messages.push(ActionMessage::SubmitOrder { order: OrderForm::new( - order.symbol.clone(), + order.pair().clone(), OrderKind::Market, - *order.details.platform(), - order.details.amount(), + *order.order_form().platform(), + order.order_form().amount(), ), }) } -- 2.47.2 From 848043d7581b92c284f59a11e5e7dfc08e53af5b Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 17 Feb 2021 16:31:47 +0000 Subject: [PATCH 124/127] changed signature of with_... functions to get Option --- rustybot/src/models.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index 9e8cc7c..2eae4bd 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -342,13 +342,13 @@ impl OrderForm { } } - pub fn with_leverage(mut self, leverage: f64) -> Self { - self.leverage = Some(leverage); + pub fn with_leverage(mut self, leverage: Option) -> Self { + self.leverage = leverage; self } - pub fn with_metadata(mut self, metadata: OrderMetadata) -> Self { - self.metadata = Some(metadata); + pub fn with_metadata(mut self, metadata: Option) -> Self { + self.metadata = metadata; self } -- 2.47.2 From 881defa081e9b151e3d0b8a861132dc8dbdfa8de Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 17 Feb 2021 16:32:08 +0000 Subject: [PATCH 125/127] enriched orderform in EnforceMarket --- rustybot/src/strategy.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 2503ba0..1052c57 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -576,7 +576,9 @@ impl OrderStrategy for MarketEnforce { OrderKind::Market, *order.order_form().platform(), order.order_form().amount(), - ), + ) + .with_leverage(order.order_form().leverage()) + .with_metadata(order.order_form().metadata().clone()), }) } -- 2.47.2 From 2d3d1ca69cf6fbe9a25a60c6c43d947382942806 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Wed, 17 Feb 2021 16:32:47 +0000 Subject: [PATCH 126/127] using right platform when sending Limit order --- rustybot/src/managers.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index ebc04ba..e2ceee2 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -14,7 +14,7 @@ use crate::connectors::{Client, ExchangeDetails}; use crate::currency::SymbolPair; use crate::events::{ActionMessage, ActorMessage, Event}; use crate::models::{ - ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker, TradingPlatform, + ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker, }; use crate::strategy::{HiddenTrailingStop, MarketEnforce, OrderStrategy, PositionStrategy}; use crate::BoxError; @@ -505,16 +505,15 @@ impl OrderManager { // No open order, undercutting best price with limit order let closing_price = self.best_closing_price(&position, &order_book); - // TODO: hardcoded platform to Derivative! let order_form = OrderForm::new( self.pair.clone(), OrderKind::Limit { price: closing_price, }, - TradingPlatform::Derivative, + position.platform(), position.amount().neg(), ) - .with_leverage(position.leverage()); + .with_leverage(Some(position.leverage())); info!("Submitting {} order", order_form.kind()); if let Err(e) = self.client.submit_order(&order_form).await { -- 2.47.2 From 3f5e6e3a70891ce114ac8b60ac829021ba583416 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Thu, 18 Feb 2021 09:37:48 +0000 Subject: [PATCH 127/127] added frontend components --- rustybot/Cargo.lock | 91 +++++++++------------------------------- rustybot/Cargo.toml | 3 +- rustybot/src/bot.rs | 3 ++ rustybot/src/frontend.rs | 90 +++++++++++++++++++++++++++++++++++++++ rustybot/src/main.rs | 11 ++--- 5 files changed, 120 insertions(+), 78 deletions(-) create mode 100644 rustybot/src/frontend.rs diff --git a/rustybot/Cargo.lock b/rustybot/Cargo.lock index 82c4d3c..1f0ae4b 100644 --- a/rustybot/Cargo.lock +++ b/rustybot/Cargo.lock @@ -87,12 +87,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - [[package]] name = "base64" version = "0.13.0" @@ -114,8 +108,8 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "tokio 1.1.0", - "tungstenite 0.12.0", + "tokio", + "tungstenite", "url", ] @@ -152,12 +146,6 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" -[[package]] -name = "bytes" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0dcbc35f504eb6fc275a6d20e4ebcda18cf50d40ba6fabff8c711fa16cb3b16" - [[package]] name = "bytes" version = "1.0.1" @@ -386,7 +374,7 @@ checksum = "fde5a672a61f96552aa5ed9fd9c81c3fbdae4be9b1e205d6eaf17c83705adc0f" dependencies = [ "futures", "pin-project-lite", - "tokio 1.1.0", + "tokio", ] [[package]] @@ -476,7 +464,7 @@ dependencies = [ "http", "indexmap", "slab", - "tokio 1.1.0", + "tokio", "tokio-util", "tracing", "tracing-futures", @@ -554,7 +542,7 @@ dependencies = [ "itoa", "pin-project 1.0.2", "socket2", - "tokio 1.1.0", + "tokio", "tower-service", "tracing", "want", @@ -569,7 +557,7 @@ dependencies = [ "bytes 1.0.1", "hyper", "native-tls", - "tokio 1.1.0", + "tokio", "tokio-native-tls", ] @@ -594,15 +582,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "input_buffer" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" -dependencies = [ - "bytes 0.5.6", -] - [[package]] name = "input_buffer" version = "0.4.0" @@ -1130,7 +1109,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd281b1030aa675fb90aa994d07187645bb3c8fc756ca766e7c3070b439de9de" dependencies = [ - "base64 0.13.0", + "base64", "bytes 1.0.1", "encoding_rs", "futures-core", @@ -1149,7 +1128,7 @@ dependencies = [ "pin-project-lite", "serde", "serde_urlencoded", - "tokio 1.1.0", + "tokio", "tokio-native-tls", "url", "wasm-bindgen", @@ -1196,8 +1175,9 @@ dependencies = [ "log 0.4.11", "merge", "regex", - "tokio 1.1.0", + "tokio", "tokio-tungstenite", + "tungstenite", ] [[package]] @@ -1396,20 +1376,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" -[[package]] -name = "tokio" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "720ba21c25078711bf456d607987d95bce90f7c3bea5abe1db587862e7a1e87c" -dependencies = [ - "autocfg", - "bytes 0.6.0", - "libc", - "memchr", - "mio", - "pin-project-lite", -] - [[package]] name = "tokio" version = "1.1.0" @@ -1448,7 +1414,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" dependencies = [ "native-tls", - "tokio 1.1.0", + "tokio", ] [[package]] @@ -1459,20 +1425,20 @@ checksum = "76066865172052eb8796c686f0b441a93df8b08d40a950b062ffb9a426f00edd" dependencies = [ "futures-core", "pin-project-lite", - "tokio 1.1.0", + "tokio", ] [[package]] name = "tokio-tungstenite" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0381c1e6e08908317cee104781ca48afe03f37cc857792b85f01f9828fb55ba3" +checksum = "e1a5f475f1b9d077ea1017ecbc60890fda8e54942d680ca0b1d2b47cfa2d861b" dependencies = [ "futures-util", "log 0.4.11", "pin-project 1.0.2", - "tokio 0.3.6", - "tungstenite 0.11.1", + "tokio", + "tungstenite", ] [[package]] @@ -1487,7 +1453,7 @@ dependencies = [ "futures-sink", "log 0.4.11", "pin-project-lite", - "tokio 1.1.0", + "tokio", "tokio-stream", ] @@ -1533,37 +1499,18 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" -[[package]] -name = "tungstenite" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0308d80d86700c5878b9ef6321f020f29b1bb9d5ff3cab25e75e23f3a492a23" -dependencies = [ - "base64 0.12.3", - "byteorder", - "bytes 0.5.6", - "http", - "httparse", - "input_buffer 0.3.1", - "log 0.4.11", - "rand 0.7.3", - "sha-1", - "url", - "utf-8", -] - [[package]] name = "tungstenite" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" dependencies = [ - "base64 0.13.0", + "base64", "byteorder", "bytes 1.0.1", "http", "httparse", - "input_buffer 0.4.0", + "input_buffer", "log 0.4.11", "native-tls", "rand 0.8.2", diff --git a/rustybot/Cargo.toml b/rustybot/Cargo.toml index 5f31416..fcc81d4 100644 --- a/rustybot/Cargo.toml +++ b/rustybot/Cargo.toml @@ -9,7 +9,6 @@ edition = "2018" [dependencies] bitfinex = { path= "/home/giulio/dev/bitfinex-rs" } tokio = { version = "1", features=["full"]} -tokio-tungstenite = "*" futures-util = { version = "0.3", default-features = false, features = ["async-await", "sink", "std"] } async-trait = "0.1" regex = "1" @@ -21,4 +20,6 @@ byteorder = "1" float-cmp = "0.8" merge = "0.1" futures-retry = "0.6" +tungstenite = "0.12" +tokio-tungstenite = "0.13" dotenv = "0.15" \ No newline at end of file diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index f9742dc..91f3ff0 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -5,6 +5,7 @@ use tokio::time::sleep; use crate::connectors::ExchangeDetails; use crate::currency::{Symbol, SymbolPair}; +use crate::frontend::FrontendManagerHandle; use crate::managers::ExchangeManager; use crate::ticker::Ticker; use crate::BoxError; @@ -12,6 +13,7 @@ use crate::BoxError; pub struct BfxBot { ticker: Ticker, exchange_managers: Vec, + frontend_connector: FrontendManagerHandle, } impl BfxBot { @@ -34,6 +36,7 @@ impl BfxBot { BfxBot { ticker: Ticker::new(tick_duration), exchange_managers, + frontend_connector: FrontendManagerHandle::new(), } } diff --git a/rustybot/src/frontend.rs b/rustybot/src/frontend.rs new file mode 100644 index 0000000..7b86c7e --- /dev/null +++ b/rustybot/src/frontend.rs @@ -0,0 +1,90 @@ +use log::info; +use tokio::sync::mpsc::{channel, Receiver, Sender}; + +use crate::events::{ActorMessage}; +use crate::BoxError; +use futures_util::stream::TryStreamExt; +use futures_util::StreamExt; + +use std::net::SocketAddr; + +use tokio::net::{TcpListener, TcpStream}; +use tokio_tungstenite::accept_async; + +#[derive(Debug)] +pub struct FrontendManager { + receiver: Receiver, +} + +impl FrontendManager { + pub fn new(receiver: Receiver) -> Self { + Self { receiver } + } + + async fn handle_ws_connection(stream: TcpStream, addr: SocketAddr) -> Result<(), BoxError> { + let websocket = accept_async(stream).await?; + info!("Received WebSocket connection <{:?}>", addr); + + let (_, ws_in) = websocket.split(); + + let on_received = ws_in.try_for_each(move |msg| { + info!( + "Received a message from {:?}: {}", + addr, + msg.to_text().unwrap() + ); + + futures_util::future::ok(()) + }); + + tokio::spawn(on_received); + + Ok(()) + } + + pub async fn websocket() -> Result<(), BoxError> { + let server = TcpListener::bind("127.0.0.1:3012").await?; + + while let Ok((stream, addr)) = server.accept().await { + tokio::spawn(FrontendManager::handle_ws_connection(stream, addr)); + } + + Ok(()) + } + + pub async fn handle_message(&mut self, message: ActorMessage) -> Result<(), BoxError> { + match message.message { + _ => {} + } + + Ok(message + .respond_to + .send((None, None)) + .map_err(|_| BoxError::from("Could not send message."))?) + } +} + +pub struct FrontendManagerHandle { + sender: Sender, +} + +impl FrontendManagerHandle { + // async fn run_frontend_manager(mut manager: FrontendManager) { + // info!("Frontend handler ready"); + // + // while let Some(msg) = manager.receiver.recv().await { + // manager.handle_message(msg).await.unwrap(); + // } + // } + + pub fn new() -> Self { + let (sender, receiver) = channel(1); + + let _frontend = FrontendManager::new(receiver); + + tokio::spawn(FrontendManager::websocket()); + // tokio::spawn(FrontendManagerHandle::run_frontend_manager(frontend)); + + Self { sender } + } +} diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index 3f76b66..3adc4d8 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -4,7 +4,7 @@ use std::env; use fern::colors::{Color, ColoredLevelConfig}; -use log::LevelFilter::{Debug, Trace}; +use log::LevelFilter::{Trace}; use tokio::time::Duration; use crate::bot::BfxBot; @@ -15,6 +15,7 @@ mod bot; mod connectors; mod currency; mod events; +mod frontend; mod managers; mod models; mod strategy; @@ -28,12 +29,12 @@ async fn main() -> Result<(), BoxError> { dotenv::dotenv()?; let api_key = env::vars() - .find(|(k, v)| k == "API_KEY") - .map(|(k, v)| v) + .find(|(k, _v)| k == "API_KEY") + .map(|(_k, v)| v) .ok_or("API_KEY not set!")?; let api_secret = env::vars() - .find(|(k, v)| k == "API_SECRET") - .map(|(k, v)| v) + .find(|(k, _v)| k == "API_SECRET") + .map(|(_k, v)| v) .ok_or("API_SECRET not set!")?; let bitfinex = ExchangeDetails::Bitfinex { -- 2.47.2