Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 23ed2e7155 | |||
| e8997ecc3e | |||
| d449a89349 | |||
| a98dcd0732 | |||
| eaaa218582 | |||
| 67396b23f1 |
@@ -0,0 +1,141 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Interfaces\MarketData;
|
||||||
|
|
||||||
|
use App\Interfaces\MarketData\Types\Ohlc;
|
||||||
|
use App\Interfaces\MarketData\Types\Quote;
|
||||||
|
use Carbon\CarbonInterval;
|
||||||
|
use Illuminate\Http\Client\PendingRequest;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
|
||||||
|
class BinanceMarketData implements MarketDataInterface
|
||||||
|
{
|
||||||
|
public PendingRequest $client;
|
||||||
|
|
||||||
|
public string $apiBaseUrl = 'https://data-api.binance.vision/api/v3/';
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->createNewClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createNewClient()
|
||||||
|
{
|
||||||
|
$this->client = Http::withOptions([
|
||||||
|
'headers' => [
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'accept' => 'application/json',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exists(string $symbol): bool
|
||||||
|
{
|
||||||
|
return (bool) $this->quote($symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function quote(string $symbol): Quote
|
||||||
|
{
|
||||||
|
$response = $this->client
|
||||||
|
->baseUrl($this->apiBaseUrl)
|
||||||
|
->withQueryParameters([
|
||||||
|
'symbol' => $symbol,
|
||||||
|
])->get('ticker');
|
||||||
|
|
||||||
|
$quote = $response->json();
|
||||||
|
|
||||||
|
throw_if(empty(Arr::get($quote, 'weightedAvgPrice')), NotFoundHttpException::class, "Symbol `{$symbol}` was not found");
|
||||||
|
|
||||||
|
$fundamental = cache()->remember(
|
||||||
|
'binance-fdmtl-'.$symbol,
|
||||||
|
1440,
|
||||||
|
function () use ($symbol) {
|
||||||
|
|
||||||
|
$this->createNewClient();
|
||||||
|
|
||||||
|
$response = $this->client
|
||||||
|
->baseUrl($this->apiBaseUrl)
|
||||||
|
->withQueryParameters(['symbol' => $symbol])
|
||||||
|
->get('exchangeInfo');
|
||||||
|
|
||||||
|
return $response->json();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return new Quote([
|
||||||
|
'name' => $symbol,
|
||||||
|
'symbol' => $symbol,
|
||||||
|
'currency' => Arr::get($fundamental, 'symbols.0.quoteAsset'),
|
||||||
|
'market_value' => (float) Arr::get($quote, 'weightedAvgPrice'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dividends(string $symbol, $startDate, $endDate): Collection
|
||||||
|
{
|
||||||
|
// noop
|
||||||
|
return collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function splits(string $symbol, $startDate, $endDate): Collection
|
||||||
|
{
|
||||||
|
// noop
|
||||||
|
return collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function history(string $symbol, $startDate, $endDate): Collection
|
||||||
|
{
|
||||||
|
$startDate = Carbon::parse($startDate);
|
||||||
|
$endDate = Carbon::parse($endDate);
|
||||||
|
|
||||||
|
$allHistory = collect();
|
||||||
|
|
||||||
|
$chunks = 500;
|
||||||
|
|
||||||
|
$period = CarbonInterval::days($chunks)->toPeriod($startDate, $endDate);
|
||||||
|
foreach ($period as $startDate) {
|
||||||
|
|
||||||
|
$chunkEnd = $startDate->copy()->addDays($chunks - 1);
|
||||||
|
|
||||||
|
if ($chunkEnd->gt($endDate)) {
|
||||||
|
$chunkEnd = $endDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->createNewClient();
|
||||||
|
|
||||||
|
$response = $this->client
|
||||||
|
->baseUrl($this->apiBaseUrl)
|
||||||
|
->withQueryParameters([
|
||||||
|
'symbol' => $symbol,
|
||||||
|
'interval' => '1d',
|
||||||
|
'startTime' => $startDate->timestamp * 1000,
|
||||||
|
'endTime' => $chunkEnd->timestamp * 1000,
|
||||||
|
])->get('klines');
|
||||||
|
|
||||||
|
$history = $response->json();
|
||||||
|
|
||||||
|
throw_if(empty($history), NotFoundHttpException::class, "Symbol `{$symbol}` was not found");
|
||||||
|
|
||||||
|
$chunkedHistory = collect($history)
|
||||||
|
->mapWithKeys(function ($history_item) use ($symbol) {
|
||||||
|
|
||||||
|
$date = Carbon::parse($history_item[0])->format('Y-m-d');
|
||||||
|
|
||||||
|
return [$date => new Ohlc([
|
||||||
|
'symbol' => $symbol,
|
||||||
|
'date' => $date,
|
||||||
|
'close' => $history_item[4],
|
||||||
|
])];
|
||||||
|
});
|
||||||
|
|
||||||
|
$allHistory = $allHistory->merge($chunkedHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $allHistory;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -99,6 +99,7 @@
|
|||||||
|
|
||||||
this.data.tooltip = {
|
this.data.tooltip = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
shared: false,
|
||||||
y: {
|
y: {
|
||||||
formatter: (value, { series, seriesIndex, dataPointIndex, w }) => {
|
formatter: (value, { series, seriesIndex, dataPointIndex, w }) => {
|
||||||
const firstDataPoint = this.data.series[seriesIndex].data[0][1]
|
const firstDataPoint = this.data.series[seriesIndex].data[0][1]
|
||||||
|
|||||||
@@ -75,26 +75,29 @@ new #[Lazy] class extends Component
|
|||||||
|
|
||||||
foreach ($dailyChange as $data) {
|
foreach ($dailyChange as $data) {
|
||||||
$date = $data->date;
|
$date = $data->date;
|
||||||
|
$marketGainData[] = [$date, round($data->total_market_gain, 2)];
|
||||||
$marketValueData[] = [$date, round($data->total_market_value, 2)];
|
$marketValueData[] = [$date, round($data->total_market_value, 2)];
|
||||||
$costBasisData[] = [$date, round($data->total_cost_basis, 2)];
|
$costBasisData[] = [$date, round($data->total_cost_basis, 2)];
|
||||||
$marketGainData[] = [$date, round($data->total_market_gain, 2)];
|
|
||||||
// $dividendSeries[] = [$date, round($data->total_dividends_earned, 2)];
|
// $dividendSeries[] = [$date, round($data->total_dividends_earned, 2)];
|
||||||
// $realizedGainSeries[] = [$date, round($data->realized_gains, 2)];
|
// $realizedGainSeries[] = [$date, round($data->realized_gains, 2)];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'series' => [
|
'series' => [
|
||||||
|
[
|
||||||
|
'name' => __('Market Gain'),
|
||||||
|
'data' => $marketGainData,
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'name' => __('Market Value'),
|
'name' => __('Market Value'),
|
||||||
'data' => $marketValueData,
|
'data' => $marketValueData,
|
||||||
|
'hidden' => true,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => __('Cost Basis'),
|
'name' => __('Cost Basis'),
|
||||||
'data' => $costBasisData,
|
'data' => $costBasisData,
|
||||||
],
|
'hidden' => true,
|
||||||
[
|
|
||||||
'name' => __('Market Gain'),
|
|
||||||
'data' => $marketGainData,
|
|
||||||
],
|
],
|
||||||
|
|
||||||
// [
|
// [
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ new class extends Component
|
|||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
@if (auth()->user()->id != $user->id)
|
@if (auth()->user()->id != $user->id)
|
||||||
<x-ui.select
|
<x-ui.select
|
||||||
class="select select-ghost border-none focus:outline-none focus:ring-0"
|
class="cursor-pointer select-ghost border-none focus:outline-none focus:ring-0"
|
||||||
:options="[['id' => 0, 'name' => __('Read only')], ['id' => 1, 'name' => __('Full access')]]"
|
:options="[['id' => 0, 'name' => __('Read only')], ['id' => 1, 'name' => __('Full access')]]"
|
||||||
wire:model.live.number="permissions.{{ $user->id }}.full_access"
|
wire:model.live.number="permissions.{{ $user->id }}.full_access"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -68,8 +68,8 @@ new class extends Component
|
|||||||
|
|
||||||
<div class="col-span-6 sm:col-span-4">
|
<div class="col-span-6 sm:col-span-4">
|
||||||
<x-ui.select
|
<x-ui.select
|
||||||
label="{{ __('Locale') }}"
|
:label="__('Locale')"
|
||||||
class="select block mt-1 w-full"
|
class=""
|
||||||
:options="config('app.available_locales')"
|
:options="config('app.available_locales')"
|
||||||
option-value="locale"
|
option-value="locale"
|
||||||
option-label="label"
|
option-label="label"
|
||||||
@@ -83,8 +83,8 @@ new class extends Component
|
|||||||
|
|
||||||
<div class="col-span-6 sm:col-span-4">
|
<div class="col-span-6 sm:col-span-4">
|
||||||
<x-ui.select
|
<x-ui.select
|
||||||
label="{{ __('Display Currency') }}"
|
:label="__('Display Currency')"
|
||||||
class="select block mt-1 w-full"
|
class=""
|
||||||
:options="$currencies"
|
:options="$currencies"
|
||||||
option-value="currency"
|
option-value="currency"
|
||||||
option-label="label"
|
option-label="label"
|
||||||
|
|||||||
Reference in New Issue
Block a user