2 && arguments[2] !== undefined ? arguments[2] : true;
- var v = 0,
- decimal = opts.decimal,
- errorOnInvalid = opts.errorOnInvalid,
- decimals = opts.precision,
- fromCents = opts.fromCents,
- precision = pow(decimals),
- isNumber = typeof value === 'number',
- isCurrency = value instanceof currency;
-
- if (isCurrency && fromCents) {
- return value.intValue;
- }
-
- if (isNumber || isCurrency) {
- v = isCurrency ? value.value : value;
- } else if (typeof value === 'string') {
- var regex = new RegExp('[^-\\d' + decimal + ']', 'g'),
- decimalString = new RegExp('\\' + decimal, 'g');
- v = value.replace(/\((.*)\)/, '-$1') // allow negative e.g. (1.99)
- .replace(regex, '') // replace any non numeric values
- .replace(decimalString, '.'); // convert any decimal values
-
- v = v || 0;
- } else {
- if (errorOnInvalid) {
- throw Error('Invalid Input');
- }
-
- v = 0;
- }
-
- if (!fromCents) {
- v *= precision; // scale number to integer value
-
- v = v.toFixed(4); // Handle additional decimal for proper rounding.
- }
-
- return useRounding ? round(v) : v;
- }
- /**
- * Formats a currency object
- * @param currency
- * @param {object} [opts]
- */
-
-
- function format(currency, settings) {
- var pattern = settings.pattern,
- negativePattern = settings.negativePattern,
- symbol = settings.symbol,
- separator = settings.separator,
- decimal = settings.decimal,
- groups = settings.groups,
- split = ('' + currency).replace(/^-/, '').split('.'),
- dollars = split[0],
- cents = split[1];
- return (currency.value >= 0 ? pattern : negativePattern).replace('!', symbol).replace('#', dollars.replace(groups, '$1' + separator) + (cents ? decimal + cents : ''));
- }
-
- currency.prototype = {
- /**
- * Adds values together.
- * @param {number} number
- * @returns {currency}
- */
- add: function add(number) {
- var intValue = this.intValue,
- _settings = this.s,
- _precision = this.p;
- return currency((intValue += parse(number, _settings)) / (_settings.fromCents ? 1 : _precision), _settings);
- },
-
- /**
- * Subtracts value.
- * @param {number} number
- * @returns {currency}
- */
- subtract: function subtract(number) {
- var intValue = this.intValue,
- _settings = this.s,
- _precision = this.p;
- return currency((intValue -= parse(number, _settings)) / (_settings.fromCents ? 1 : _precision), _settings);
- },
-
- /**
- * Multiplies values.
- * @param {number} number
- * @returns {currency}
- */
- multiply: function multiply(number) {
- var intValue = this.intValue,
- _settings = this.s;
- return currency((intValue *= number) / (_settings.fromCents ? 1 : pow(_settings.precision)), _settings);
- },
-
- /**
- * Divides value.
- * @param {number} number
- * @returns {currency}
- */
- divide: function divide(number) {
- var intValue = this.intValue,
- _settings = this.s;
- return currency(intValue /= parse(number, _settings, false), _settings);
- },
-
- /**
- * Takes the currency amount and distributes the values evenly. Any extra pennies
- * left over from the distribution will be stacked onto the first set of entries.
- * @param {number} count
- * @returns {array}
- */
- distribute: function distribute(count) {
- var intValue = this.intValue,
- _precision = this.p,
- _settings = this.s,
- distribution = [],
- split = Math[intValue >= 0 ? 'floor' : 'ceil'](intValue / count),
- pennies = Math.abs(intValue - split * count),
- precision = _settings.fromCents ? 1 : _precision;
-
- for (; count !== 0; count--) {
- var item = currency(split / precision, _settings); // Add any left over pennies
-
- pennies-- > 0 && (item = item[intValue >= 0 ? 'add' : 'subtract'](1 / precision));
- distribution.push(item);
- }
-
- return distribution;
- },
-
- /**
- * Returns the dollar value.
- * @returns {number}
- */
- dollars: function dollars() {
- return ~~this.value;
- },
-
- /**
- * Returns the cent value.
- * @returns {number}
- */
- cents: function cents() {
- var intValue = this.intValue,
- _precision = this.p;
- return ~~(intValue % _precision);
- },
-
- /**
- * Formats the value as a string according to the formatting settings.
- * @param {boolean} useSymbol - format with currency symbol
- * @returns {string}
- */
- format: function format(options) {
- var _settings = this.s;
-
- if (typeof options === 'function') {
- return options(this, _settings);
- }
-
- return _settings.format(this, Object.assign({}, _settings, options));
- },
-
- /**
- * Formats the value as a string according to the formatting settings.
- * @returns {string}
- */
- toString: function toString() {
- var intValue = this.intValue,
- _precision = this.p,
- _settings = this.s;
- return rounding(intValue / _precision, _settings.increment).toFixed(_settings.precision);
- },
-
- /**
- * Value for JSON serialization.
- * @returns {float}
- */
- toJSON: function toJSON() {
- return this.value;
- }
- };
-
- return currency;
-
-})));
diff --git a/node_modules/currency.js/license b/node_modules/currency.js/license
deleted file mode 100644
index 6a5ef5d..0000000
--- a/node_modules/currency.js/license
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2018 Jason Wilson
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
\ No newline at end of file
diff --git a/node_modules/currency.js/package.json b/node_modules/currency.js/package.json
deleted file mode 100644
index 28a00ee..0000000
--- a/node_modules/currency.js/package.json
+++ /dev/null
@@ -1,99 +0,0 @@
-{
- "name": "currency.js",
- "homepage": "http://scurker.github.io/currency.js",
- "version": "2.0.4",
- "description": "A small, lightweight javascript library for working with currency values.",
- "main": "dist/currency.js",
- "module": "dist/currency.es.js",
- "js:next": "dist/currency.es.js",
- "browser": "dist/currency.min.js",
- "engines": {
- "node": ">=4"
- },
- "directories": {
- "test": "test"
- },
- "scripts": {
- "build": "npm-run-all clean transpile copy-typescript-definition copy-flow-definition",
- "build:docs": "npm-run-all clean:docs compile:docs minify:docs",
- "clean": "rm -rf dist/*",
- "clean:docs": "rm -rf docs/build",
- "compile:docs": "npx babel-node --presets=@babel/preset-env ./docs/src/build-docs.js",
- "coverage": "BABEL_ENV=test nyc ava ./test/test.js",
- "coverage:report": "nyc report --reporter=text-lcov > lcov.info",
- "copy-typescript-definition": "cp -f src/currency.d.ts dist",
- "copy-flow-definition": "cp -f src/currency.js.flow dist",
- "lint": "eslint .",
- "minify:docs": "html-minifier --input-dir ./docs/build --output-dir ./docs/build --file-ext html --collapse-whitespace --decode-entities --minify-css --minify-js",
- "prepare": "npm run build",
- "pretest:js": "npm run transpile:es",
- "pretest:typescript": "npm run copy-typescript-definition",
- "pretest:flow": "npm run copy-flow-definition",
- "transpile:es": "rollup -c ./config/rollup.config.js",
- "transpile:umd": "rollup -c ./config/rollup.umd.js",
- "transpile": "npm-run-all transpile:*",
- "test": "npm-run-all test:typescript test:flow test:js",
- "test:js": "BABEL_ENV=test ava ./test/test.js",
- "test:typescript": "tsc -p ./test --noEmit",
- "test:flow": "flow"
- },
- "repository": {
- "type": "git",
- "url": "git://github.com/scurker/currency.js.git"
- },
- "files": [
- "dist"
- ],
- "typings": "./dist/currency.d.ts",
- "keywords": [
- "currency",
- "money",
- "utilities",
- "accounting",
- "format",
- "number",
- "parse",
- "precision",
- "decimal"
- ],
- "author": "Jason Wilson",
- "license": "MIT",
- "bugs": {
- "url": "https://github.com/scurker/currency.js/issues"
- },
- "devDependencies": {
- "@babel/cli": "^7.10.4",
- "@babel/core": "^7.10.4",
- "@babel/node": "^7.10.4",
- "@babel/preset-env": "^7.10.4",
- "@babel/preset-flow": "^7.10.4",
- "@babel/register": "^7.5.5",
- "@scurker/eslint-config": "^1.1.5",
- "ava": "^2.2.0",
- "cheerio": "^1.0.0-rc",
- "eslint": "^7.4.0",
- "flow-bin": "^0.151.0",
- "google-closure-compiler-js": "^20200719.0.0",
- "gzip-size": "^6.0.0",
- "handlebars": "^4.7.6",
- "highlight.js": "^10.0.0",
- "html-minifier": "^4.0.0",
- "matchdep": "^2.0.0",
- "metalsmith": "^2.3.0",
- "metalsmith-ignore": "^1.0.0",
- "metalsmith-markdown": "^1.3.0",
- "minimatch": "^3.0.4",
- "npm-run-all": "^4.1.5",
- "nyc": "^15.1.0",
- "pretty-bytes": "^5.3.0",
- "rollup": "^2.21.0",
- "rollup-plugin-babel": "^4.4.0",
- "sinon": "^10.0.0",
- "typescript": "^4.0.2"
- },
- "nyc": {
- "include": [
- "dist/**/*.js"
- ]
- }
-}
diff --git a/node_modules/currency.js/readme.md b/node_modules/currency.js/readme.md
deleted file mode 100644
index dd69ff4..0000000
--- a/node_modules/currency.js/readme.md
+++ /dev/null
@@ -1,179 +0,0 @@
-
-
-![currency.js logo](https://user-images.githubusercontent.com/1062039/31397824-9dfa15f0-adac-11e7-9869-fb20746e90c1.png)
-
-# currency.js
-
-[![Build Status](https://github.com/scurker/currency.js/actions/workflows/ci.yml/badge.svg)](https://github.com/scurker/currency.js/actions/workflows/ci.yml)
-[![Coverage Status](https://coveralls.io/repos/scurker/currency.js/badge.svg?branch=master&service=github)](https://coveralls.io/github/scurker/currency.js?branch=master)
-[![npm](https://img.shields.io/npm/v/currency.js.svg?style=flat)](https://www.npmjs.com/package/currency.js)
-[![gzip size](http://img.badgesize.io/https://unpkg.com/currency.js/dist/currency.min.js?compression=gzip)](https://unpkg.com/currency.js/dist/currency.min.js)
-
-
-
-*currency.js* is a lightweight ~1kb javascript library for working with currency values. It was built to work around floating point issues in javascript. This [talk by Bartek Szopka](http://www.youtube.com/watch?v=MqHDDtVYJRI) explains in detail why javascript has floating point issues.
-
-currency.js works with values as integers behind the scenes, resolving some of the most basic precision problems.
-
-```javascript
-2.51 + .01; // 2.5199999999999996
-currency(2.51).add(.01); // 2.52
-
-2.52 - .01; // 2.5100000000000002
-currency(2.52).subtract(.01); // 2.51
-```
-
-This should work for most *reasonable* values of currencies. As long as your currency values are less than 253 (in cents) or 90,071,992,547,409.91 you should be okay.
-
-### Features
-
-* *0 dependencies!*
-* Immutable
-* Flexible formatting options
-* Handles *any* type of currency input, strings, numbers, or another currency instance
-
-### Installation
-
-With [npm](https://www.npmjs.com/):
-
-```sh
-npm install --save currency.js
-```
-
-With [yarn](https://yarnpkg.com):
-
-```sh
-yarn add currency.js
-```
-
-Via cdn:
-
-```html
-
-```
-
-Need the latest cutting edge? All commits on master are tagged with `next` on npm:
-
-```sh
-npm install --save currency.js@next
-```
-
-### Usage
-
-Currency will accept numbers, strings, or the currency object itself as values.
-
-```javascript
-currency(123); // 123.00
-currency(1.23); // 1.23
-currency("1.23") // 1.23
-currency("$12.30") // 12.30
-
-var value = currency("123.45");
-currency(value); // 123.45
-```
-
-Currency accepts decimal values (i.e. `1.23`) with a default precision of 2, but can accept a minor currency unit (e.g. cents in a dollar). This will respect the precision option when parsing.
-
-```javascript
-currency(123, { fromCents: true }); // 1.23
-currency('123', { fromCents: true }); // 1.23
-currency(123, { fromCents: true, precision: 0 }); // 123
-currency(123, { fromCents: true, precision: 3 }); // 0.123
-```
-
-There's various arithmetic methods that help take the guesswork out of trying to resolve floating point problems.
-
-```javascript
-currency(123.50).add(0.23); // 123.73
-currency(5.00).subtract(0.50); // 4.50
-currency(45.25).multiply(3); // 135.75
-currency(1.12).distribute(5); // [0.23, 0.23, 0.22, 0.22, 0.22]
-```
-
-There's even a built in formatter that will automatically place comma delimiters in the right place.
-
-```javascript
-currency("2,573,693.75").add("100,275.50").format(); // "$2,673,969.25"
-currency("1,237.72").subtract(300).format(); // "$937.72"
-```
-
-You can also change the format, localizing the decimal and/or delimiter to your locale.
-
-```javascript
-var euro = value => currency(value, { symbol: "€", separator: ".", decimal: "," });
-euro("2.573.693,75").add("100.275,50").format(); // "€2.673.969,25"
-euro("1.237,72").subtract(300).format(); // "€937,72"
-```
-
-### Options
-
-*currency.js* comes with its own set of default options conforming to USD. You can customize these according to your locale.
-
-`symbol` *default*: `$`
-Currency symbol included when calling `currency.format()`.
-
-`separator` *default*: `,`
-Separator dividing the number groups when calling `currency.format()`.
-
-`decimal` *default*: `.`
-Decimal used when calling `currency.format()`.
-
-`precision` *default*: `2`
-Number of decimal places to store as cents.
-
-`pattern` *default*: `!#`
-Allows you to customize the format pattern using `!` as replacement for the currency symbol and `#` as replacement for the currency amount.
-
-`negativePattern` *default*: `-!#`
-Allows you to customize the negative format pattern using `!` as replacement for the currency symbol and `#` as replacement for the currency amount.
-
-`format` *default* `null`
-Allows you to customize the format of the currency when calling `currency.format()`. `format` passes in the `currency` object as well as the `options` object to the function and expects a string to be returned. Use this when the provided formatting options do not meet your needs.
-
-`errorOnInvalid` *default*: `false`
-If an invalid value such as `null` or `undefined` is passed in, will throw an error.
-
-`increment` *default*: `null`
-When implementing a currency that implements rounding, setting the increment value will allow you to set the closest increment to round the display value to. `currency(1.48, { increment: .05 }); // => 1.50`
-
-`useVedic` *default*: `false`
-Formats number groupings using the Indian Numbering System, i.e. `10,00,000.00`
-
-`fromCents` *default*: `false`
-Parse the amount value as a minor currency unit (e.g. cents in a dollar) instead of dollars.
-
-> View more examples and full documentation at [https://currency.js.org](https://currency.js.org).
-
-### Internationalization Examples
-
-```js
-currency(1.23, { separator: " ", decimal: ",", symbol: "€" });
-```
-
-If you need to work with multiple currency values, the easiest way is to setup factory functions with your required currency settings.
-
-```js
-const USD = value => currency(value, { symbol: "$", precision: 2 });
-const JPY = value => currency(value, { symbol: "¥", precision: 0 });
-const GAS = value => currency(value, { precision: 3 });
-
-USD(1234.56).format(); // "$1,234.56"
-JPY(1234.56).format(); // "¥1,235"
-GAS(1234.56).format(); // "$1,234.560"
-```
-
-## Add-ons
-
-* [babel-plugin-transform-currency-operators](https://github.com/scurker/babel-plugin-transform-currency-operators): An experimental babel plugin for transforming currency operations `currency(1.23) + 4.56` to `currency(1.23).add(4.56)`.
-
-## Other Libraries
-
-Maybe currency.js isn't the right fit for your needs. Check out some of these other fine libraries:
-
-* [accounting.js](https://github.com/openexchangerates/accounting.js)
-* [dinero.js](https://github.com/sarahdayan/dinero.js)
-* [walletjs](https://github.com/dleitee/walletjs)
-
-## License
-
-[MIT](/license)
diff --git a/package.json b/package.json
index 23d231e..ccbf052 100644
--- a/package.json
+++ b/package.json
@@ -1,10 +1,18 @@
{
- "name": "name",
- "description": "description",
- "authors": "author",
- "version": "1.0.0",
- "main": "pathToMain",
- "dependencies": {
- "currency.js": "^2.0.4"
- }
-}
\ No newline at end of file
+ "name": "name",
+ "description": "description",
+ "authors": "author",
+ "version": "1.0.0",
+ "main": "pathToMain",
+ "dependencies": {
+ "currency.js": "^2.0.4",
+ "mathjs": "^14.0.0"
+ },
+ "devDependencies": {
+ "@types/chrome": "^0.0.287",
+ "@types/bun": "latest"
+ },
+ "scripts": {
+ "pack": "bun build --minify src/btc.js > dist.js && zip ext.zip dist.js manifest.json images/*"
+ }
+}
diff --git a/src/btc.ts b/src/btc.ts
new file mode 100644
index 0000000..ccd9350
--- /dev/null
+++ b/src/btc.ts
@@ -0,0 +1,108 @@
+import currency from "currency.js";
+import { CURRENCIES, REGEX } from "model";
+
+async function convertPrices() {
+ const currencyMap: Record = {};
+ const elements = document.querySelectorAll("*");
+ const existingTooltipElements: HTMLElement[] = [];
+
+ for (const e of elements) {
+ const element = e as HTMLElement;
+ const matches = element.textContent?.match(REGEX);
+ if (!matches) continue;
+ if (element.dataset.processed === "true") continue;
+
+ element.dataset.processed = "true";
+
+ const currencyCode = CURRENCIES[matches[1] as keyof typeof CURRENCIES].code;
+ const storageKey = `${currencyCode}-exchangeRate`;
+
+ if (!currencyMap[currencyCode]) {
+ const storageData = await chrome.storage.local.get(storageKey);
+ if (!storageData || Object.keys(storageData).length === 0) {
+ await updateExchangeRateToLocalStorage(currencyCode);
+ }
+ const { [storageKey]: exchangeRate } = await chrome.storage.local.get(
+ storageKey
+ );
+ currencyMap[currencyCode] = exchangeRate;
+ }
+
+ const exchangeRate = currencyMap[currencyCode];
+ const originalText = element.textContent!;
+ const price = currency(originalText, {
+ symbol: matches[1],
+ precision: 8,
+ decimal: ",",
+ });
+ const bitcoinPrice = price.divide(exchangeRate);
+ const bitsPrice = currency(bitcoinPrice.multiply(1e6), { precision: 2 });
+
+ const tooltip = bitsPrice.format({
+ symbol: "bits",
+ decimal: ".",
+ separator: " ",
+ pattern: "# !",
+ });
+
+ // Add tooltip
+ element.dataset.btc = tooltip;
+
+ existingTooltipElements.push(element);
+ }
+
+ await Promise.resolve();
+ existingTooltipElements.forEach((e) => {
+ if (existingTooltipElements.some((it) => it.contains(e) && it !== e)) {
+ e.dataset.btc = "";
+ }
+ });
+}
+
+async function updateExchangeRateToLocalStorage(currency: string = "USD") {
+ const { lastUpdate } = await chrome.storage.local.get(
+ `${currency}-exchangeRateTimestamp`
+ );
+
+ if (lastUpdate && new Date().getTime() - lastUpdate < 3600 * 1000) return;
+
+ try {
+ const response = await fetch(
+ `https://api.coindesk.com/v1/bpi/currentprice/${currency}.json`
+ );
+ const data = await response.json();
+ if (!data?.bpi?.[currency])
+ throw new Error(`Exchange rate data not found for ${currency}`);
+ await chrome.storage.local.set({
+ [`${currency}-exchangeRate`]: data.bpi[currency].rate_float,
+ [`${currency}-exchangeRateTimestamp`]: Date.now(),
+ });
+ } catch (error) {
+ console.error(error);
+ }
+}
+
+// Create a style element
+const style = document.createElement("style");
+
+// Add CSS rules
+style.textContent = `
+[data-btc]:hover::after {
+ display: block;
+ position: absolute;
+ content: attr(data-btc);
+ border: 1px solid black;
+ background: #eee;
+ padding: .25em;
+ z-index: 99;
+}
+`;
+
+// Append the style element to the document head
+document.head.appendChild(style);
+
+const observer = new MutationObserver(convertPrices);
+const config = { childList: true, subtree: true };
+observer.observe(document, config);
+
+setInterval(updateExchangeRateToLocalStorage, 300 * 1000);
diff --git a/src/hpr.spec.ts b/src/hpr.spec.ts
new file mode 100644
index 0000000..8cadb58
--- /dev/null
+++ b/src/hpr.spec.ts
@@ -0,0 +1,13 @@
+import { expect, test } from "bun:test";
+import { halvingPriceBand } from "hpr";
+
+test("halvingPriceBand", () => {
+ expect(halvingPriceBand(54000, "2024-12-01")).toBeGreaterThanOrEqual(-0.25);
+ expect(halvingPriceBand(80000, "2024-12-01")).toBeLessThanOrEqual(0.21);
+ expect(halvingPriceBand(81000, "2024-12-01")).toBeGreaterThanOrEqual(0.21);
+ expect(halvingPriceBand(114000, "2024-12-01")).toBeLessThanOrEqual(0.57);
+ expect(halvingPriceBand(130000, "2024-12-01")).toBe("Yellow");
+ expect(halvingPriceBand(200000, "2024-12-01")).toBe("Orange");
+ expect(halvingPriceBand(250000, "2024-12-01")).toBe("Red");
+ expect(halvingPriceBand(310000, "2024-12-01")).toBe("Black");
+});
diff --git a/src/hpr.ts b/src/hpr.ts
new file mode 100644
index 0000000..7daf429
--- /dev/null
+++ b/src/hpr.ts
@@ -0,0 +1,72 @@
+export function getBandName(index: number) {
+ if (index < -0.5) return "White";
+ if (-0.5 <= index && index <= 0.5) return "Blue";
+ if (0.5 <= index && index <= 1.5) return "Green";
+ if (1.5 <= index && index <= 2.5) return "Yellow";
+ if (2.5 <= index && index <= 3.5) return "Orange";
+ if (3.5 <= index && index <= 4.5) return "Red";
+ return "Black";
+}
+
+// Helper function to fit a logarithmic regression model and return the parameters
+function fitLogRegression(
+ xData: number[],
+ yData: number[]
+): { a: number; b: number } {
+ const n = xData.length;
+ let sumX = 0,
+ sumY = 0,
+ sumXY = 0,
+ sumX2 = 0;
+
+ for (let i = 0; i < n; i++) {
+ sumX += Math.log(xData[i]);
+ sumY += Math.log10(yData[i]);
+ sumXY += Math.log(xData[i]) * Math.log10(yData[i]);
+ sumX2 += Math.log(xData[i]) ** 2;
+ }
+
+ // Solving for a and b
+ const a = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX ** 2);
+ const b = (sumY - a * sumX) / n;
+
+ return { a, b };
+}
+
+export function halvingPriceBand(price: number, date: string): number {
+ const bands = ["Green", "Yellow", "Orange", "Red"];
+
+ // Halving dates and prices
+ const halvingDates = [
+ new Date(2012, 10, 28), // November is month 10 (0-indexed)
+ new Date(2016, 6, 9), // July is month 6 (0-indexed)
+ new Date(2020, 4, 11), // May is month 4 (0-indexed)
+ ];
+ const halvingPrices = [12.33, 651.94, 8591.65];
+
+ // Convert halving dates to days since Genesis Block (2009-01-03)
+ const genesisBlock = new Date(2009, 0, 3);
+ const xHalvingDates = halvingDates.map((date) =>
+ Math.floor(
+ (date.getTime() - genesisBlock.getTime()) / (1000 * 60 * 60 * 24)
+ )
+ );
+
+ // Fit the model parameters
+ const { a, b } = fitLogRegression(xHalvingDates, halvingPrices);
+
+ // Parse input date and find xInput (days since Genesis Block)
+ const inputDate = new Date(date);
+ const xInput = Math.floor(
+ (inputDate.getTime() - genesisBlock.getTime()) / (1000 * 60 * 60 * 24)
+ );
+
+ // Calculate the HPR value using the fitted logarithmic model
+ const logPrice = a * Math.log(xInput) + b;
+ const hprPrice = Math.pow(10, logPrice);
+
+ // Find the relative difference from the HPR value
+ const difference = price / hprPrice;
+
+ return difference - 1;
+}
diff --git a/src/model.ts b/src/model.ts
new file mode 100644
index 0000000..26c6430
--- /dev/null
+++ b/src/model.ts
@@ -0,0 +1,69 @@
+type Currency = {
+ code: string;
+ symbol: string;
+ decimal_digits: number;
+ rounding: number;
+ thousands_sep: string;
+ decimal_sep: string;
+ symbol_first: boolean;
+};
+
+// TODO: Find a currency json file
+export const CURRENCIES = {
+ R$: {
+ code: "BRL",
+ symbol: "R$",
+ decimal_digits: 2,
+ rounding: 0,
+ thousands_sep: ".",
+ decimal_sep: ",",
+ symbol_first: true,
+ },
+ "€": {
+ code: "EUR",
+ symbol: "€",
+ decimal_digits: 2,
+ rounding: 0,
+ thousands_sep: ".",
+ decimal_sep: ",",
+ symbol_first: true,
+ },
+ "¥": {
+ code: "JPY",
+ symbol: "¥",
+ decimal_digits: 0,
+ rounding: 0,
+ thousands_sep: ",",
+ decimal_sep: ".",
+ symbol_first: true,
+ },
+ "£": {
+ code: "GBP",
+ symbol: "£",
+ decimal_digits: 2,
+ rounding: 0,
+ thousands_sep: ".",
+ decimal_sep: ",",
+ symbol_first: true,
+ },
+ $: {
+ code: "USD",
+ symbol: "$",
+ decimal_digits: 2,
+ rounding: 0,
+ thousands_sep: ",",
+ decimal_sep: ".",
+ symbol_first: true,
+ },
+};
+
+const MONEY_TOKENS: string[] = Object.values(CURRENCIES)
+ .map((currency: Currency) => currency.symbol)
+ .concat(Object.values(CURRENCIES).map((currency: Currency) => currency.code));
+
+export const REGEX = new RegExp(
+ `^(${MONEY_TOKENS.map((token) => token.replace("$", "\\$")).join(
+ "|"
+ )})\\s?((\\d+(\\.|,|')?)+\\d)$`,
+ "i"
+);
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..f7cd4f0
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "target": "es2020",
+ "module": "commonjs",
+ "strict": true,
+ "esModuleInterop": true,
+ "outDir": "dist",
+ "rootDir": "src",
+ "sourceMap": true,
+ "noImplicitAny": true,
+ "noEmit": true,
+ "moduleResolution": "node",
+ "allowSyntheticDefaultImports": true,
+ "skipLibCheck": true,
+ "baseUrl": "src"
+ },
+ "include": ["src/**/*.ts"]
+}