Feat plugin stock (#196)

This commit is contained in:
Simon Lecoq
2021-03-21 17:52:05 +01:00
committed by GitHub
parent 89182c62c0
commit ed5dc9a53c
11 changed files with 330 additions and 5 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View File

@@ -28,11 +28,13 @@
} }
/**Formatter */ /**Formatter */
export function format(n, {sign = false} = {}) { export function format(n, {sign = false, unit = true} = {}) {
if (unit) {
for (const {u, v} of [{u:"b", v:10**9}, {u:"m", v:10**6}, {u:"k", v:10**3}]) { for (const {u, v} of [{u:"b", v:10**9}, {u:"m", v:10**6}, {u:"k", v:10**3}]) {
if (n/v >= 1) if (n/v >= 1)
return `${(sign)&&(n > 0) ? "+" : ""}${(n/v).toFixed(2).substr(0, 4).replace(/[.]0*$/, "")}${u}` return `${(sign)&&(n > 0) ? "+" : ""}${(n/v).toFixed(2).substr(0, 4).replace(/[.]0*$/, "")}${u}`
} }
}
return `${(sign)&&(n > 0) ? "+" : ""}${n}` return `${(sign)&&(n > 0) ? "+" : ""}${n}`
} }

View File

@@ -0,0 +1,75 @@
/**Mocked data */
export default function({faker, url, options, login = faker.internet.userName()}) {
//Wakatime api
if (/^https:..apidojo-yahoo-finance-v1.p.rapidapi.com.stock.v2.*$/.test(url)) {
//Get company profile
if (/get-profile/.test(url)) {
console.debug(`metrics/compute/mocks > mocking yahoo finance api result > ${url}`)
return ({
status:200,
data:{
price:{
marketCap:{
raw:faker.random.number(1000000000),
},
symbol:"OCTO",
},
quoteType:{
shortName:faker.company.companyName(),
longName:faker.company.companyName(),
exchangeTimezoneName:faker.address.timeZone(),
symbol:"OCTO",
},
calendarEvents:{},
summaryDetail:{},
symbol:"OCTO",
assetProfile:{
fullTimeEmployees:faker.random.number(10000),
city:faker.address.city(),
country:faker.address.country(),
},
},
})
}
//Get stock chart
if (/get-chart/.test(url)) {
console.debug(`metrics/compute/mocks > mocking yahoo finance api result > ${url}`)
return ({
status:200,
data:{
chart:{
result:[
{
meta:{
currency:"USD",
symbol:"OCTO",
regularMarketPrice:faker.random.number(10000)/100,
chartPreviousClose:faker.random.number(10000)/100,
previousClose:faker.random.number(10000)/100,
},
timestamp:new Array(1000).fill(Date.now()).map((x, i) => x+i*60000),
indicators:{
quote:[
{
close:new Array(1000).fill(null).map(_ => faker.random.number(10000)/100),
get low() {
return this.close
},
get high() {
return this.close
},
get open() {
return this.close
},
volume:[],
},
],
},
},
],
},
},
})
}
}
}

View File

@@ -230,7 +230,7 @@
favorites:distribution(7).map((value, index, array) => ({name:faker.lorem.word(), color:faker.internet.color(), value, size:faker.random.number(1000000), x:array.slice(0, index).reduce((a, b) => a + b, 0)})) favorites:distribution(7).map((value, index, array) => ({name:faker.lorem.word(), color:faker.internet.color(), value, size:faker.random.number(1000000), x:array.slice(0, index).reduce((a, b) => a + b, 0)}))
} }
}) : null), }) : null),
//Languages //RSS
...(set.plugins.enabled.rss ? ({ ...(set.plugins.enabled.rss ? ({
rss:{ rss:{
source:faker.lorem.words(), source:faker.lorem.words(),
@@ -242,6 +242,20 @@
})), })),
} }
}) : null), }) : null),
//Stock price
...(set.plugins.enabled.stock ? ({
stock:{
chart:"(stock chart is not displayed in placeholder)",
currency:"USD",
price:faker.random.number(10000)/100,
previous:faker.random.number(10000)/100,
get delta() { return this.price-this.previous },
symbol:options["stock.symbol"],
company:faker.company.companyName(),
interval:options["stock.interval"],
duration:options["stock.duration"],
}
}) : null),
//Habits //Habits
...(set.plugins.enabled.habits ? ({ ...(set.plugins.enabled.habits ? ({
habits:{ habits:{

View File

@@ -0,0 +1,34 @@
### 💹 Stock prices
The *stock* plugin lets you display the stock market price of a given company.
<table>
<td align="center">
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.stock.svg">
<img width="900" height="1" alt="">
</td>
</table>
<details>
<summary>💬 Obtaining a RapidAPI Yahoo Finance token</summary>
Create a [RapidAPI account](https://rapidapi.com) and subscribe to [Yahoo Finance API](https://rapidapi.com/apidojo/api/yahoo-finance1) to get a token.
![RapidAPI token](/.github/readme/imgs/plugin_stock_token.png)
</details>
#### Examples workflows
[➡️ Available options for this plugin](metadata.yml)
```yaml
- uses: lowlighter/metrics@latest
with:
# ... other options
plugin_stock: yes
plugin_stock_token: ${{ secrets.STOCK_TOKEN }} # RapidAPI Yahoo Finance token
plugin_stock_symbol: TSLA # Display Tesla stock price
plugin_stock_duration: 1d # Display last day of market
plugin_stock_interval: 5m # Use precision of 5 minutes for each record
```

View File

@@ -0,0 +1,60 @@
//Setup
export default async function({login, q, imports, data, account}, {enabled = false, token} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
if ((!enabled)||(!q.stock))
return null
//Load inputs
let {symbol, interval, duration} = imports.metadata.plugins.stock.inputs({data, account, q})
if (!token)
throw {error:{message:"A token is required"}}
if (!symbol)
throw {error:{message:"A company stock symbol is required"}}
symbol = symbol.toLocaleUpperCase()
//Query API for company informations
console.debug(`metrics/compute/${login}/plugins > stock > querying api for company`)
const {data:{quoteType:{shortName:company}}} = await imports.axios.get("https://apidojo-yahoo-finance-v1.p.rapidapi.com/stock/v2/get-profile", {
params:{symbol, region:"US"},
headers:{"x-rapidapi-key":token},
})
//Query API for sotck charts
console.debug(`metrics/compute/${login}/plugins > stock > querying api for stock`)
const {data:{chart:{result:[{meta, timestamp, indicators:{quote:[{close}]}}]}}} = await imports.axios.get("https://apidojo-yahoo-finance-v1.p.rapidapi.com/stock/v2/get-chart", {
params:{interval, symbol, range:duration, region:"US"},
headers:{"x-rapidapi-key":token},
})
const {currency, regularMarketPrice:price, previousClose:previous} = meta
//Generating chart
console.debug(`metrics/compute/${login}/plugins > stock > generating chart`)
const chart = await imports.chartist("line", {
width:480,
height:160,
showPoint:false,
axisX:{showGrid:false, labelInterpolationFnc:(value, index) => index%Math.floor(close.length/4) === 0 ? value : null},
axisY:{scaleMinSpace:20},
showArea:true,
}, {
labels:timestamp.map(timestamp => new Intl.DateTimeFormat("en-GB", {month:"2-digit", day:"2-digit", hour:"2-digit", minute:"2-digit"}).format(new Date(timestamp*1000))),
series:[close],
})
//Results
return {chart, currency, price, previous, delta:price-previous, symbol, company, interval, duration}
}
//Handle errors
catch (error) {
let message = "An error occured"
if (error.isAxiosError) {
const status = error.response?.status
const description = error.response?.data?.message ?? null
message = `API returned ${status}${description ? ` (${description})` : ""}`
error = error.response?.data ?? null
}
throw {error:{message, instance:error}}
}
}

View File

@@ -0,0 +1,58 @@
name: "💹 Stock prices"
cost: N/A
categorie: other
index: 1
supports:
- user
- organization
inputs:
# Enable or disable plugin
plugin_stock:
description: Display stock prices of a given company
type: boolean
default: no
# RapidAPI Yahoo finance token
# Case insensitive
plugin_stock_token:
description: Yahoo Finance token
type: token
default: ""
# Company stock symbol (required)
plugin_stock_symbol:
description: Company stock symbol
type: string
default: ""
# Time range to display (relative to current date)
plugin_stock_duration:
description: Time range to display
type: string
default: 1d
values:
- 1d # Today
- 5d # 5 days
- 1mo # 1 month
- 3mo # 3 months
- 6mo # 6 months
- 1y # 1 year
- 2y # 2 years
- 5y # 5 years
- 10y # 10 years
- ytd # Year to date
- max # All time
# Time invervals between each records over the given time range
plugin_stock_interval:
description: Time intervals between records
type: string
default: 5m
values:
- 1m # 1 minute
- 2m # 2 minutes
- 5m # 5 minutes
- 15m # 15 minutes
- 60m # 60 minutes
- 1d # 1 day

View File

@@ -0,0 +1,17 @@
- name: Stock plugin (default)
uses: lowlighter/metrics@latest
with:
token: NOT_NEEDED
plugin_stock: yes
plugin_stock_token: MOCKED_TOKEN
plugin_stock_symbol: OCTO
- name: Stock plugin (complete)
uses: lowlighter/metrics@latest
with:
token: NOT_NEEDED
plugin_stock: yes
plugin_stock_token: MOCKED_TOKEN
plugin_stock_symbol: OCTO
plugin_stock_duration: 5d
plugin_stock_interval: 5m

View File

@@ -25,5 +25,6 @@
"wakatime", "wakatime",
"skyline", "skyline",
"stackoverflow", "stackoverflow",
"stock",
"achievements" "achievements"
] ]

View File

@@ -0,0 +1,48 @@
<% if (plugins.stock) { %>
<section>
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M10.75 9a.75.75 0 000 1.5h1.5a.75.75 0 000-1.5h-1.5z"></path><path fill-rule="evenodd" d="M0 3.75C0 2.784.784 2 1.75 2h12.5c.966 0 1.75.784 1.75 1.75v8.5A1.75 1.75 0 0114.25 14H1.75A1.75 1.75 0 010 12.25v-8.5zm14.5 0V5h-13V3.75a.25.25 0 01.25-.25h12.5a.25.25 0 01.25.25zm0 2.75h-13v5.75c0 .138.112.25.25.25h12.5a.25.25 0 00.25-.25V6.5z"></path></svg>
Stock prices <%= plugins.stock.symbol ? `for ${plugins.stock.symbol}` : "" %>
</h2>
<% if (plugins.stock.error) { %>
<div class="row fill-width">
<section>
<div class="field error">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.343 13.657A8 8 0 1113.657 2.343 8 8 0 012.343 13.657zM6.03 4.97a.75.75 0 00-1.06 1.06L6.94 8 4.97 9.97a.75.75 0 101.06 1.06L8 9.06l1.97 1.97a.75.75 0 101.06-1.06L9.06 8l1.97-1.97a.75.75 0 10-1.06-1.06L8 6.94 6.03 4.97z"></path></svg>
<%= plugins.stock.error.message %>
</div>
</section>
</div>
<% } else { %>
<div class="row">
<section>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 14.25c0 .138.112.25.25.25H4v-1.25a.75.75 0 01.75-.75h2.5a.75.75 0 01.75.75v1.25h2.25a.25.25 0 00.25-.25V1.75a.25.25 0 00-.25-.25h-8.5a.25.25 0 00-.25.25v12.5zM1.75 16A1.75 1.75 0 010 14.25V1.75C0 .784.784 0 1.75 0h8.5C11.216 0 12 .784 12 1.75v12.5c0 .085-.006.168-.018.25h2.268a.25.25 0 00.25-.25V8.285a.25.25 0 00-.111-.208l-1.055-.703a.75.75 0 11.832-1.248l1.055.703c.487.325.779.871.779 1.456v5.965A1.75 1.75 0 0114.25 16h-3.5a.75.75 0 01-.197-.026c-.099.017-.2.026-.303.026h-3a.75.75 0 01-.75-.75V14h-1v1.25a.75.75 0 01-.75.75h-3zM3 3.75A.75.75 0 013.75 3h.5a.75.75 0 010 1.5h-.5A.75.75 0 013 3.75zM3.75 6a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5zM3 9.75A.75.75 0 013.75 9h.5a.75.75 0 010 1.5h-.5A.75.75 0 013 9.75zM7.75 9a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5zM7 6.75A.75.75 0 017.75 6h.5a.75.75 0 010 1.5h-.5A.75.75 0 017 6.75zM7.75 3a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5z"></path></svg>
<%= plugins.stock.company %>
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M6 2a.75.75 0 01.696.471L10 10.731l1.304-3.26A.75.75 0 0112 7h3.25a.75.75 0 010 1.5h-2.742l-1.812 4.528a.75.75 0 01-1.392 0L6 4.77 4.696 8.03A.75.75 0 014 8.5H.75a.75.75 0 010-1.5h2.742l1.812-4.529A.75.75 0 016 2z"></path></svg>
Valued at <%= plugins.stock.price.toFixed(2) %> <%= plugins.stock.currency %>
</div>
</section>
<section>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4.75 0a.75.75 0 01.75.75V2h5V.75a.75.75 0 011.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0113.25 16H2.75A1.75 1.75 0 011 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 014.75 0zm0 3.5h8.5a.25.25 0 01.25.25V6h-11V3.75a.25.25 0 01.25-.25h2zm-2.25 4v6.75c0 .138.112.25.25.25h10.5a.25.25 0 00.25-.25V7.5h-11z"></path></svg>
<%= {"1d":"Today", "5d":"Last five days", "1mo":"Last month", "3mo":"Last trimester", "6mo":"Last semester", "1y":"Last year", "2y":"Last two years", "5y":"Last five years", "10y":"Last ten years", ytd:"Year to date", max:"All-time"}[plugins.stock.duration] %>
</div>
<div class="field">
<% if (plugins.stock.delta > 0) { %>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M3.47 7.78a.75.75 0 010-1.06l4.25-4.25a.75.75 0 011.06 0l4.25 4.25a.75.75 0 01-1.06 1.06L9 4.81v7.44a.75.75 0 01-1.5 0V4.81L4.53 7.78a.75.75 0 01-1.06 0z"></path></svg>
<% } else { %>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M13.03 8.22a.75.75 0 010 1.06l-4.25 4.25a.75.75 0 01-1.06 0L3.47 9.28a.75.75 0 011.06-1.06l2.97 2.97V3.75a.75.75 0 011.5 0v7.44l2.97-2.97a.75.75 0 011.06 0z"></path></svg>
<% } %>
<%= f(plugins.stock.delta.toFixed(2), {sign:true}) %> (<%= f((100*plugins.stock.delta/plugins.stock.price).toFixed(2), {sign:true}) %>%)
</div>
</section>
</div>
<div class="stock-chart">
<%- plugins.stock.chart %>
</div>
<% } %>
</section>
<% } %>

View File

@@ -916,6 +916,22 @@
color: #666666; color: #666666;
} }
/* Charts */
.ct-line {
stroke-width: 2px !important;
stroke: #58A6FF !important;
}
.ct-area {
fill: #58A6FF !important;
}
.ct-label {
fill: rgba(127, 127, 127, 0.8) !important;
color: rgba(127, 127, 127, 0.8) !important;
}
.ct-grid {
stroke: rgba(127, 127, 127, 0.4) !important;
}
/* Fade animation */ /* Fade animation */
.af { .af {
opacity: 0; opacity: 0;