Compare commits

...

45 Commits

Author SHA1 Message Date
hackerESQ 997b5420ee Merge pull request #44 from investbrainapp/create-docker-image
Use the investbrainapp/investbrain docker image
2024-12-17 18:28:03 -06:00
hackerESQ 643bbe3af2 wait for db to be ready in order to migrate 2024-12-16 21:54:59 -06:00
hackerESQ f85f0f19b9 wip 2024-12-16 21:33:45 -06:00
hackerESQ 3f9a1bafa0 Merge pull request #37 from investbrainapp/feat-use-openai-compatible-endpoints
Feat use openai compatible endpoints
2024-12-06 16:10:54 -06:00
hackerESQ 6f72a03ecf fix: specifically use factory import 2024-12-06 16:10:27 -06:00
hackerESQ 5b8e4c634e fix: remove validation which could prevent selfhosting w ollama 2024-12-06 16:09:03 -06:00
hackerESQ 70c3f7162e Merge pull request #36 from investbrainapp/feat-use-openai-compatible-endpoints
feat: adds ollama support
2024-12-06 16:02:13 -06:00
hackerESQ cb9199431a feat: adds ollama support 2024-12-06 16:01:53 -06:00
hackerESQ cba9fe1e7b feat: adds pagination to recent activity list 2024-11-29 14:22:35 -06:00
hackerESQ baa49e77eb docs: fix link to import / export section 2024-11-27 12:52:37 -06:00
hackerESQ b015462e50 docs: adds information about import / export capabilities
See #25
2024-11-27 12:51:52 -06:00
hackerESQ c9f1fc1bea feat: make the system prompt aware of current date 2024-11-14 23:44:04 -06:00
hackerESQ 1177886271 Merge pull request #32 from investbrainapp/remove-terms-checkbox-for-self-hosted
Remove terms checkbox for self hosted
2024-11-14 02:23:50 -06:00
hackerESQ 0e1c56dd18 feat: do not show terms when self hosting 2024-11-14 02:23:22 -06:00
hackerESQ eefe237dff feat: allow app debug on default install 2024-11-14 02:10:21 -06:00
hackerESQ 8d4e004177 Merge pull request #31 from investbrainapp/dev
Uses last dividend created date as start date instead of last dividend date
2024-11-14 01:30:27 -06:00
hackerESQ 1c63e2b856 fix: uses last dividend created date as start date instead of last dividend date
closes #26
2024-11-14 01:25:03 -06:00
hackerESQ 3040cbf49a chore: bump composer deps 2024-11-12 22:42:27 -06:00
hackerESQ 1a124a2571 Merge pull request #30 from investbrainapp/dev
fix: refresh dividends only once daily
2024-11-12 22:36:50 -06:00
hackerESQ 26c8c3f3b9 Merge pull request #29 from investbrainapp/dependabot/composer/laravel/framework-11.31.0
chore(deps): bump laravel/framework from 11.30.0 to 11.31.0
2024-11-12 20:56:50 -06:00
dependabot[bot] 50d814ebf6 chore(deps): bump laravel/framework from 11.30.0 to 11.31.0
Bumps [laravel/framework](https://github.com/laravel/framework) from 11.30.0 to 11.31.0.
- [Release notes](https://github.com/laravel/framework/releases)
- [Changelog](https://github.com/laravel/framework/blob/11.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/framework/compare/v11.30.0...v11.31.0)

---
updated-dependencies:
- dependency-name: laravel/framework
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-12 22:47:25 +00:00
hackerESQ 7fc20876dd fix: refresh dividends only once daily 2024-11-12 00:53:22 -06:00
hackerESQ 183108400e Merge pull request #17 from investbrainapp/dependabot/npm_and_yarn/vite-5.4.10
chore(deps-dev): bump vite from 5.3.5 to 5.4.10
2024-11-08 21:03:32 -06:00
dependabot[bot] 3055d34979 chore(deps-dev): bump vite from 5.3.5 to 5.4.10
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.3.5 to 5.4.10.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.10/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.10/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-09 03:01:41 +00:00
hackerESQ 747f5f5f42 Merge pull request #16 from investbrainapp/dependabot/npm_and_yarn/axios-1.7.4
chore(deps-dev): bump axios from 1.7.3 to 1.7.4
2024-11-08 21:00:30 -06:00
dependabot[bot] 4db9409b94 chore(deps-dev): bump axios from 1.7.3 to 1.7.4
Bumps [axios](https://github.com/axios/axios) from 1.7.3 to 1.7.4.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.3...v1.7.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-08 04:10:36 +00:00
hackerESQ 8693bb29ca Update README.md 2024-11-07 22:08:46 -06:00
hackerESQ 524d8ca41d Update SECURITY.md 2024-11-07 22:08:16 -06:00
hackerESQ 3c77eca689 Create SECURITY.md 2024-11-07 22:02:53 -06:00
hackerESQ 307f74b1d9 Merge pull request #14 from investbrainapp/dev
fix: adds validation for transaction date
2024-11-07 20:46:03 -06:00
hackerESQ 0c29393f3b fix: skip dividend sync if most recent dividend was less than 24 hours ago 2024-11-07 20:40:55 -06:00
hackerESQ af3726cb91 fix: sales quantity should use rounded float numbers 2024-11-07 18:20:53 -06:00
hackerESQ 0d40fd92f0 fix: adds validation for transaction date (no post-dated transactions) 2024-11-07 18:06:27 -06:00
hackerESQ 0f55d84355 Merge pull request #13 from eltociear/patch-1
docs: update README.md
2024-11-07 17:42:31 -06:00
Ikko Eltociear Ashimine eafa889827 docs: update README.md
assstant -> assistant
2024-11-08 08:39:39 +09:00
hackerESQ 60cd880c2e docs: fix formatting for TOC 2024-11-07 17:31:50 -06:00
hackerESQ ea8de69863 docs: adds table of contents to docs 2024-11-07 17:31:17 -06:00
hackerESQ 11ef26e878 docs: add troubleshooting section 2024-11-07 17:05:25 -06:00
hackerESQ 2770ebf958 Merge pull request #12 from investbrainapp/dev
fix:ensure portfolio is available on transactions page
2024-11-07 16:23:03 -06:00
hackerESQ 536ca56c24 fix:ensure portfolio is available on transactions page 2024-11-07 16:18:29 -06:00
hackerESQ d4407d3492 Merge pull request #10 from investbrainapp/dev
Make ASSET_URL optional in env
2024-11-07 12:20:08 -06:00
hackerESQ 81766b4aba fix:don't force set asset_url
related to discussion #7
2024-11-07 12:16:49 -06:00
hackerESQ e1cc040984 Merge pull request #6 from investbrainapp/dev
chore: minor updates for landing page
2024-11-06 23:07:40 -06:00
hackerESQ cdda9d7ff7 chore:maintain lock file 2024-11-06 23:02:31 -06:00
hackerESQ 9b6afe180d fix:logic for selfhosted landing page 2024-11-06 23:01:56 -06:00
28 changed files with 722 additions and 622 deletions
+4 -4
View File
@@ -1,14 +1,15 @@
APP_NAME=Investbrain
APP_ENV=production
APP_KEY=
APP_DEBUG=false
APP_DEBUG=true
APP_TIMEZONE=UTC
APP_PORT=8000
APP_URL="http://localhost:${APP_PORT}"
ASSET_URL="${APP_URL}"
SELF_HOSTED=true
REGISTRATION_ENABLED=true
# ASSET_URL="http://localhost:8000" # (optional) webroot for static assets (css, js, images, etc)
AI_CHAT_ENABLED=false
OPENAI_API_KEY=
OPENAI_ORGANIZATION=
@@ -32,7 +33,6 @@ APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
DB_CONNECTION=mysql
DB_HOST=investbrain-mysql
DB_PORT=3306
@@ -52,7 +52,7 @@ QUEUE_CONNECTION=redis
CACHE_STORE=redis
REDIS_CLIENT=predis
REDIS_HOST=127.0.0.1
REDIS_HOST=investbrain-redis
REDIS_PATH=/tmp/database_server.sock
REDIS_PASSWORD=null
REDIS_PORT=6379
+57 -5
View File
@@ -6,9 +6,21 @@ Investbrain is a smart open-source investment tracker that helps you manage, tra
<p align="center"><a href="https://investbra.in" target="_blank"><img src="https://raw.githubusercontent.com/investbrainapp/investbrain/main/screenshot.png" width="100%" alt="Investbrain Screenshot"></a></p>
## Table of contents
- [Under the hood](#under-the-hood)
- [Install (self hosting)](#self-hosting)
- [Chat with your holdings](#chat-with-your-holdings)
- [Market data providers](#market-data-providers)
- [Import / Export](#import--export)
- [Configuration](#configuration)
- [Updating](#updating)
- [Command line utilities](#command-line-utilities)
- [Troubleshooting](#troubleshooting)
- [Testing](#testing)
## Under the hood
Investbrain is a Laravel PHP web application that leverages Livewire and Tailwind for its frontend. Most databases should work, including MySQL and SQLite. Out of the box, we feature three market data providers: [Yahoo Finance](https://finance.yahoo.com/), [Finnhub](https://finnhub.io/pricing-stock-api-market-data), and [Alpha Vantage](https://www.alphavantage.co/support/). But we also offer an extensible market data provider interface for intrepid developers to create their own! We also offer an integration with OpenAI's LLMs for our ["chat with your holdings"](#chat-with-your-holdings) capability. Finally, of course we have robust support for i18n, a11y, and dark mode.
Investbrain is a Laravel PHP web application that leverages Livewire and Tailwind for its frontend. Most databases should work, including MySQL and SQLite. Out of the box, we feature three market data providers: [Yahoo Finance](https://finance.yahoo.com/), [Finnhub](https://finnhub.io/pricing-stock-api-market-data), and [Alpha Vantage](https://www.alphavantage.co/support/). But we also offer an extensible market data provider interface for intrepid developers to create their own! We also offer an integration with OpenAI for our ["chat with your holdings"](#chat-with-your-holdings) capability. Finally, of course we have robust support for i18n, a11y, and dark mode.
## Self hosting
@@ -44,9 +56,11 @@ Congrats! You've just installed Investbrain!
Investbrain offers an AI powered chat assistant that is grounded on *your* investments. This enables you to use AI as a thought partner when making investment decisions.
When self-hosting, you can enable the chat assstant by configuring your OpenAI Secret Key and Organization ID in your [.env](https://github.com/investbrainapp/investbrain/blob/main/.env.example) file. Navigate to OpenAI to [create your keys](https://platform.openai.com/api-keys).
When self-hosting, you can enable the chat assistant by configuring your OpenAI Secret Key and Organization ID in your [.env](https://github.com/investbrainapp/investbrain/blob/main/.env.example) file. Navigate to OpenAI to [create your keys](https://platform.openai.com/api-keys).
Always keep in mind the limitations of large language models. When in doubt, consult a licensed investment advisor.
If you are self-hosting your own large language models ("LLMs") that expose an OpenAI compatible API (e.g. [Ollama](https://ollama.com/blog/openai-compatibility)), you can update the `OPENAI_BASE_URI` configuration to your self-hosted instance. Ensure you also update the `OPENAI_MODEL` to an available model.
Always keep in mind the limitations of LLMs. When in doubt, consult a licensed investment advisor.
## Market data providers
@@ -93,6 +107,18 @@ MARKET_DATA_PROVIDER=yahoo,alphavantage,custom_provider
Feel free to submit a PR with any custom providers you create.
## Import / Export
Investbrain includes a convenient feature which allows you to import and export portfolios and transaction data.
### Import
Imports are "upserted" to the database. If the record does not already exist in the database, the record will be created. However, when a portfolio or transaction exists (the record's ID matches an existing record), the record will be updated. This way, you can simultaneously create new records, but also bulk update records.
### Export
Exporting your portfolios and transactions is a convenient way to back-up your Investbrain data. It is also a convenient way to maintain portability of *your* data.
## Configuration
There are several optional configurations available when installing using the recommended [Docker method](#self-hosting). These options are configurable using an environment file. Changes can be made in your [.env](https://github.com/investbrainapp/investbrain/blob/main/.env.example) file before installation.
@@ -109,11 +135,12 @@ There are several optional configurations available when installing using the re
| AI_CHAT_ENABLED | Whether to enable AI chat features | `false` |
| OPENAI_API_KEY | OpenAI secret key (required for AI chat) | `null` |
| OPENAI_ORGANIZATION | OpenAI org id (required for AI chat) | `null` |
| OPENAI_MODEL | The selected LLM used for AI chat | gpt-4o |
| OPENAI_BASE_URI | The URI for your self-hosted LLM | api.openai.com/v1 |
| DAILY_CHANGE_TIME | The time of day to capture daily change | 23:00 |
| REGISTRATION_ENABLED | Whether to enable registration of new users | `true` |
> Note: These options affect the [docker-compose.yml](https://github.com/investbrainapp/investbrain/blob/main/docker-compose.yml) file, so if you decide to make any changes to these default configurations, you'll have to restart the Docker containers before your changes take effect.
## Updating
@@ -159,6 +186,31 @@ Just to be safe, we recommend backing up your portfolios before using these comm
| sync:daily-change | Re-calculates daily snapshots of your portfolio's daily performance. Useful to fill in gaps in your portfolio charts. (Note: this is an extremely resource intensive query.) |
| sync:holdings | Re-calculates performance of holdings with related transactions (i.e. dividends, realized gains, etc). |
## Troubleshooting
If you are facing issues with Investbrain, it can be handy to monitor the application's logs:
```bash
docker exec -it investbrain-app cat storage/logs/laravel.log
```
or you can live monitor logs using `tail`:
```bash
docker exec -it investbrain-app tail -f storage/logs/laravel.log
```
### Common issues
<details>
**<summary>Market data not refreshing on fresh install</summary>**
If you're unable to refresh market data out of the box (i.e. your market data provider is set to Yahoo), there is a chance Yahoo is being blocked by a firewall or adblocker. Pihole is known to block `fc.yahoo.com` which is the domain used to query Yahoo.
Once you whitelist `fc.yahoo.com` in pihole, your market data should begin populating!
</details>
## Testing
Investbrain has a robus PHPUnit test suite that creates an in-memory SQLite database and runs any queued jobs synchronously using Laravel's array driver. You can run the entire Investbrain test suite from within the Docker container by running:
@@ -185,7 +237,7 @@ We ask that you be kind and polite when interacting with the Investbrain communi
## Security Vulnerabilities
If you discover a security vulnerability within Investbrain, please create an issue in the [Github repository](https://github.com/investbrainapp/investbrain). All security vulnerabilities will be promptly addressed.
If you discover a security vulnerability within Investbrain, please submit your report via [Github](https://github.com/investbrainapp/investbrain/security/advisories/new). All security vulnerabilities will be promptly addressed. We ask that you keep any suspected vulnerabilities private and confidential until they have been appropriately addressed.
## License
+12
View File
@@ -0,0 +1,12 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 1.0.x | :white_check_mark: |
| < 1.0.0 | :x: |
## Reporting a Vulnerability
If you discover a security vulnerability within Investbrain, please submit your report via [Github](https://github.com/investbrainapp/investbrain/security/advisories/new). All security vulnerabilities will be promptly addressed. We ask that you keep any suspected vulnerabilities private and confidential until they have been appropriately addressed.
+11 -9
View File
@@ -27,7 +27,7 @@ class Dividend extends Model
protected $casts = [
'date' => 'datetime',
'last_date' => 'datetime',
'last_dividend_update' => 'datetime',
];
public function marketData() {
@@ -50,25 +50,29 @@ class Dividend extends Model
/**
* Grab new dividend data
*
* @param string $symbol
* @return void
*/
public static function refreshDividendData(string $symbol)
public static function refreshDividendData(string $symbol): void
{
$dividends_meta = self::where(['symbol' => $symbol])
->selectRaw('COUNT(symbol) as total_dividends')
->selectRaw('MAX(date) as last_date')
->selectRaw('MAX(created_at) as last_dividend_update')
->get()
->first();
// assume we need to populate ALL dividend data
$start_date = new \DateTime('@0');
$start_date = new Carbon('@0');
$end_date = now();
// nope, refresh forward looking only
if ( $dividends_meta->total_dividends ) {
$start_date = $dividends_meta->last_dividend_update->addHours(24);
}
// skip refresh if there's already recent data
if ($start_date->greaterThan($end_date)) {
$start_date = $dividends_meta->last_date->addHours(48);
return;
}
// get some data
@@ -99,8 +103,6 @@ class Dividend extends Model
$market_data->last_dividend_amount = $dividend_data->sortByDesc('date')->first()['dividend_amount'];
$market_data->save();
}
return $dividend_data;
}
public static function syncHoldings(string $symbol): void
+1 -1
View File
@@ -125,7 +125,7 @@ class Transaction extends Model
public function scopeBeforeDate($query, $date)
{
return $query->whereDate('date', '<', $date);
return $query->whereDate('date', '<=', $date);
}
public function scopeMyTransactions()
@@ -2,7 +2,10 @@
namespace App\Providers;
use Illuminate\Support\Arr;
use Laravel\Jetstream\Features;
use App\Actions\Jetstream\DeleteUser;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\ServiceProvider;
use Laravel\Jetstream\Jetstream;
@@ -26,6 +29,13 @@ class JetstreamServiceProvider extends ServiceProvider
Jetstream::deleteUsersUsing(DeleteUser::class);
if ( config('investbrain.self_hosted', false) ) {
Config::set(
'jetstream.features',
array_keys(Arr::except(array_values(config('jetstream.features')), Features::termsAndPrivacyPolicy()))
);
}
}
/**
+1 -1
View File
@@ -50,7 +50,7 @@ class QuantityValidationRule implements ValidationRule
$maxQuantity = $purchase_qty - $sales_qty;
if ($value > $maxQuantity) {
if (round($value, 3) > round($maxQuantity, 3)) {
$fail(__('The quantity must not be greater than the available quantity.'));
}
}
+1 -1
View File
@@ -16,7 +16,7 @@
"livewire/livewire": "^3.5",
"livewire/volt": "^1.6",
"maatwebsite/excel": "^3.1",
"openai-php/laravel": "^0.10.2",
"openai-php/client": "^0.10.3",
"predis/predis": "^2.2",
"robsontenorio/mary": "^1.35",
"scheb/yahoo-finance-api": "^4.11",
Generated
+468 -496
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -143,7 +143,7 @@ return [
|
*/
'inject_morph_markers' => true,
'inject_morph_markers' => false,
/*
|---------------------------------------------------------------------------
+1
View File
@@ -27,5 +27,6 @@ return [
'request_timeout' => env('OPENAI_REQUEST_TIMEOUT', 30),
//
'base_uri' => env('OPENAI_BASE_URI', 'api.openai.com/v1'),
'model' => env('OPENAI_MODEL', 'gpt-4o'),
];
+12 -3
View File
@@ -3,9 +3,7 @@ networks:
driver: bridge
services:
app:
build:
context: .
dockerfile: docker/Dockerfile
image: investbrainapp/investbrain:latest
container_name: investbrain-app
restart: unless-stopped
tty: true
@@ -15,8 +13,18 @@ services:
- .:/var/www/app:delegated
depends_on:
- mysql
- redis
networks:
- investbrain-network
redis:
image: redis:alpine
container_name: investbrain-redis
restart: unless-stopped
tty: true
networks:
- investbrain-network
volumes:
- investbrain-redis:/data
nginx:
image: nginx:alpine
container_name: investbrain-nginx
@@ -46,4 +54,5 @@ services:
networks:
- investbrain-network
volumes:
investbrain-redis:
investbrain-mysql:
-1
View File
@@ -1 +0,0 @@
dump.rdb
-47
View File
@@ -1,47 +0,0 @@
FROM php:8.3-fpm
ENV DEBIAN_FRONTEND noninteractive
# Set the working directory
COPY . /var/www/app
WORKDIR /var/www/app
# Install common php extension dependencies
RUN apt-get update && apt-get install -y \
libfreetype-dev \
libjpeg62-turbo-dev \
libpng-dev \
zlib1g-dev \
libzip-dev \
unzip \
libicu-dev \
git \
curl \
redis \
supervisor \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
gd \
zip \
pdo_mysql \
mysqli \
intl
# Install Node.js and npm
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y nodejs \
&& npm install -g npm@latest
# Copy over supervisor configuration
COPY ./docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# Update permissions
RUN chown -R www-data:www-data . \
&& chmod -R 775 ./storage \
&& chmod +x ./docker/entrypoint.sh
# install composer
COPY --from=composer:2.6.5 /usr/bin/composer /usr/local/bin/composer
# Run everything else
CMD ["./docker/entrypoint.sh"]
Executable → Regular
+14 -4
View File
@@ -9,9 +9,6 @@ if [ ! -f ".env" ]; then
cp .env.example .env
fi
echo "====================== Checking for updates... ====================== "
/usr/bin/git pull
echo "====================== Installing Composer dependencies... ====================== "
/usr/local/bin/composer install
@@ -38,7 +35,20 @@ echo "====================== Installing NPM dependencies and building frontend..
/usr/bin/npm run build
echo "====================== Running migrations... ====================== "
/usr/local/bin/php artisan migrate --force
run_migrations() {
/usr/local/bin/php artisan migrate --force
}
RETRIES=30
DELAY=5
until run_migrations; do
RETRIES=$((RETRIES-1))
if [ $RETRIES -le 0 ]; then
echo "Database is not ready after multiple attempts. Exiting..."
exit 1
fi
echo "Waiting for database to be ready... retrying in $DELAY seconds."
sleep $DELAY
done
echo "====================== Spinning up Supervisor daemon... ====================== "
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
-2
View File
@@ -1,2 +0,0 @@
# Redis RDB and AOF file location
dir /var/www/app/docker
-7
View File
@@ -11,13 +11,6 @@ autorestart=true
stdout_logfile=/var/log/supervisor/php.log
stderr_logfile=/var/log/supervisor/php_error.log
[program:redis]
command=redis-server /var/www/app/docker/redis.conf
autostart=true
autorestart=true
stdout_logfile=/var/log/supervisor/redis.log
stderr_logfile=/var/log/supervisor/redis_error.log
[program:scheduler]
command=php artisan schedule:work
autorestart=true
+26 -22
View File
@@ -1,5 +1,5 @@
{
"name": "investbrain",
"name": "app",
"lockfileVersion": 3,
"requires": true,
"packages": {
@@ -11,12 +11,12 @@
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10",
"autoprefixer": "^10.4.19",
"axios": "^1.6.4",
"axios": "^1.7.4",
"daisyui": "^4.12.10",
"laravel-vite-plugin": "^1.0",
"postcss": "^8.4.40",
"tailwindcss": "^3.4.7",
"vite": "^5.0"
"vite": "^5.4"
}
},
"node_modules/@alloc/quick-lru": {
@@ -862,9 +862,9 @@
}
},
"node_modules/axios": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz",
"integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==",
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
"dev": true,
"dependencies": {
"follow-redirects": "^1.15.6",
@@ -1721,9 +1721,9 @@
}
},
"node_modules/picocolors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true
},
"node_modules/picomatch": {
@@ -1757,9 +1757,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.40",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz",
"integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==",
"version": "8.4.47",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
"dev": true,
"funding": [
{
@@ -1777,8 +1777,8 @@
],
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.1",
"source-map-js": "^1.2.0"
"picocolors": "^1.1.0",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -2090,9 +2090,9 @@
}
},
"node_modules/source-map-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
@@ -2437,14 +2437,14 @@
"dev": true
},
"node_modules/vite": {
"version": "5.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz",
"integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==",
"version": "5.4.10",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz",
"integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==",
"dev": true,
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.39",
"rollup": "^4.13.0"
"postcss": "^8.4.43",
"rollup": "^4.20.0"
},
"bin": {
"vite": "bin/vite.js"
@@ -2463,6 +2463,7 @@
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
@@ -2480,6 +2481,9 @@
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
+2 -2
View File
@@ -9,12 +9,12 @@
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10",
"autoprefixer": "^10.4.19",
"axios": "^1.6.4",
"axios": "^1.7.4",
"daisyui": "^4.12.10",
"laravel-vite-plugin": "^1.0",
"postcss": "^8.4.40",
"tailwindcss": "^3.4.7",
"vite": "^5.0"
"vite": "^5.4"
},
"dependencies": {
"apexcharts": "^3.51.0"
+3 -1
View File
@@ -63,7 +63,9 @@
<x-ib-card title="{{ __('Recent activity') }}" class="md:col-span-3">
@livewire('transactions-list', [
'transactions' => $user->transactions
'transactions' => $user->transactions,
'showPortfolio' => true,
'paginate' => false
])
</x-ib-card>
+1 -1
View File
@@ -207,7 +207,7 @@
* 52 week high: {$holding->market_data->fifty_two_week_high}
* Dividend yield: {$holding->market_data->dividend_yield}
Based on this current market data, quantity owned, and average cost basis, you should determine if the {$holding->symbol} holding is making or losing money.
This data is current as of today's date: " . now()->format('Y-m-d') . ". Based on this current market data, quantity owned, and average cost basis, you should determine if the {$holding->symbol} holding is making or losing money.
Below is the question from the investor. Considering these facts, provide a concise response to the following question (give a direct response). Limit your response to no more than 75 words and consider using a common decision framework. Use github style markdown for any formatting:"
])
@@ -5,7 +5,7 @@ use App\Models\AiChat;
use App\Models\Holding;
use Illuminate\Database\Eloquent\Model;
use Livewire\Volt\Component;
use OpenAI\Laravel\Facades\OpenAI;
use OpenAI\Factory;
use OpenAI\Responses\StreamResponse;
new class extends Component {
@@ -67,7 +67,9 @@ new class extends Component {
{
try {
$stream = OpenAI::chat()->createStreamed([
$client = $this->createOpenAiClient();
$stream = $client->chat()->createStreamed([
'model' => config('openai.model'),
'messages' => [
['role' => 'system', 'content' => "Today's date is "
@@ -104,7 +106,9 @@ new class extends Component {
public function generateSuggestedPrompts(): void
{
try {
$suggested_prompts = OpenAI::chat()->create([
$client = $this->createOpenAiClient();
$suggested_prompts = $client->chat()->create([
'model' => config('openai.model'),
'response_format' => [
'type' => 'json_schema',
@@ -192,6 +196,21 @@ new class extends Component {
return false;
}
private function createOpenAiClient()
{
$apiKey = config('openai.api_key');
$organization = config('openai.organization');
$baseUri = config('openai.base_uri');
return OpenAI::factory()
->withApiKey($apiKey)
->withOrganization($organization)
->withHttpHeader('OpenAI-Beta', 'assistants=v2')
->withHttpClient(new \GuzzleHttp\Client(['timeout' => config('openai.request_timeout', 30)]))
->withBaseUri($baseUri)
->make();
}
}; ?>
<div
@@ -36,7 +36,7 @@ new class extends Component {
'symbol' => ['required', 'string', new SymbolValidationRule],
'transaction_type' => 'required|string|in:BUY,SELL',
'portfolio_id' => 'required|exists:portfolios,id',
'date' => 'required|date_format:Y-m-d',
'date' => ['required', 'date_format:Y-m-d', 'before_or_equal:' . now()->format('Y-m-d')],
'quantity' => [
'required',
'numeric',
@@ -83,14 +83,14 @@ new class extends Component {
public function save()
{
$this->authorize('fullAccess', $this->portfolio);
$validated = $this->validate();
if (!isset($this->portfolio)) {
$this->portfolio = Portfolio::find($this->portfolio_id);
}
$this->authorize('fullAccess', $this->portfolio);
$validated = $this->validate();
$transaction = $this->portfolio->transactions()->create($validated);
$transaction->save();
@@ -15,6 +15,10 @@ new class extends Component {
public ?Portfolio $portfolio;
public ?Transaction $editingTransaction;
public Bool $shouldGoToHolding = true;
public Bool $showPortfolio = false;
public Bool $paginate = true;
public Int $perPage = 5;
public Int $offset = 0;
protected $listeners = [
'transaction-updated' => '$refresh',
@@ -38,17 +42,23 @@ new class extends Component {
return $this->redirect(route('holding.show', ['portfolio' => $holding['portfolio_id'], 'symbol' => $holding['symbol']]));
}
public function updateOffset($amount = 0)
{
$this->offset = $this->offset + $amount;
}
}; ?>
<div class="">
@foreach($transactions->sortByDesc('date')->take(10) as $transaction)
@foreach($transactions->sortByDesc('date')->slice($offset)->take($perPage) as $transaction)
<x-list-item
no-separator
:item="$transaction"
class="cursor-pointer"
x-data="{ loading: false, timeout: null }"
:key="$transaction->id"
@click="
if ($wire.shouldGoToHolding) {
@@ -83,12 +93,44 @@ new class extends Component {
<x-loading x-show="loading" x-cloak class="text-gray-400 ml-2" />
</x-slot:value>
<x-slot:sub-value>
@if($showPortfolio)
<span title="{{ __('Portfolio') }}">{{ $transaction->portfolio->title }} </span>
&middot;
@endif
<span title="{{ __('Transaction Date') }}">{{ $transaction->date->format('F j, Y') }} </span>
</x-slot:sub-value>
</x-list-item>
@endforeach
@if ($paginate && count($transactions) > $perPage)
<div class="flex justify-between">
<span>
@if($offset > 0)
<x-button
class="btn btn-sm btn-ghost text-secondary"
wire:click="updateOffset(-{{ $perPage }})"
>
{!! __('pagination.previous') !!}
</x-button>
@endif
</span>
<span>
@if(count($transactions) - $offset > $offset)
<x-button
class="btn btn-sm btn-ghost text-secondary"
wire:click="updateOffset({{ $perPage }})"
>
{!! __('pagination.next') !!}
</x-button>
@endif
</span>
</div>
@endif
<x-ib-alpine-modal
key="manage-transaction"
title="{{ __('Manage Transaction') }}"
@@ -96,7 +138,7 @@ new class extends Component {
@livewire('manage-transaction-form', [
'portfolio' => $portfolio,
'transaction' => $editingTransaction,
], key($editingTransaction->id ?? 'new'))
], key($editingTransaction?->id.rand()))
</x-ib-alpine-modal>
</div>
+1 -1
View File
@@ -173,7 +173,7 @@
{$formattedHoldings}
Based on the current market data, quantity owned, and average cost basis, you can determine the performance of any holding.
This data is current as of today's date: " . now()->format('Y-m-d') . ". Based on the current market data, quantity owned, and average cost basis, you can determine the performance of any holding.
Below is the question from the investor. Considering these facts, provide a concise response to the following question (give a direct response). Limit your response to no more than 75 words and consider using a common decision framework. Use github style markdown for any formatting:"
])
+2 -2
View File
@@ -8,7 +8,7 @@ use App\Console\Commands\{RefreshMarketData, CaptureDailyChange, RefreshDividend
* This scheduled job refreshes market data from your selected data provider
* Update the cadence with the MARKET_DATA_REFRESH key in your env file
*/
Schedule::command(RefreshMarketData::class)->everyMinute()->weekdays();
Schedule::command(RefreshMarketData::class)->weekdays()->everyMinute();
/**
*
@@ -20,7 +20,7 @@ Schedule::command(CaptureDailyChange::class)->dailyAt(config('investbrain.daily_
*
* Refreshes dividend data for your holdings (and syncs new dividends to holdings)
*/
Schedule::command(RefreshDividendData::class)->days([1, 3, 5])->weekdays();
Schedule::command(RefreshDividendData::class)->daily()->days([1, 3, 5]);
/**
*
+1 -1
View File
@@ -12,7 +12,7 @@ use Laravel\Jetstream\Http\Controllers\Livewire\PrivacyPolicyController;
use Laravel\Jetstream\Http\Controllers\Livewire\TermsOfServiceController;
Route::get('/', function () {
if (config('investbrain.self_hosted', false) && View::exists('landing-page::index')) {
if (!config('investbrain.self_hosted', true) && View::exists('landing-page::index')) {
return view('landing-page::index');
}
+22
View File
@@ -61,4 +61,26 @@ class DividendsTest extends TestCase
$this->assertCount(3, $transactions);
$this->assertEqualsWithDelta(4.95, $dividendsReinvested * $market_data->market_value, 0.01);
}
/**
*/
public function test_do_not_duplicate_recent_dividends(): void
{
$this->actingAs($user = User::factory()->create());
$portfolio = Portfolio::factory()->create();
Transaction::factory()->buy()->yearsAgo()->portfolio($portfolio->id)->symbol('ACME')->create();
$holding = Holding::query()->portfolio($portfolio->id)->symbol('ACME')->first();
Dividend::create([
'symbol' => 'ACME',
'date' => now()->subDay(2),
'dividend_amount' => .01
]);
Dividend::refreshDividendData('ACME');
$this->assertCount(1, $holding->dividends);
}
}