79 lines
2.4 KiB
Dart
79 lines
2.4 KiB
Dart
/// Alpaca API credentials and endpoints (server-only).
|
|
class AlpacaEnv {
|
|
AlpacaEnv({
|
|
required this.apiKeyId,
|
|
required this.apiSecretKey,
|
|
required this.tradingBaseUrl,
|
|
required this.dataBaseUrl,
|
|
required this.dataFeed,
|
|
required this.allowLive,
|
|
});
|
|
|
|
static const String defaultPaperTradingUrl =
|
|
'https://paper-api.alpaca.markets';
|
|
static const String defaultDataUrl = 'https://data.alpaca.markets';
|
|
static const String liveTradingHost = 'api.alpaca.markets';
|
|
|
|
final String apiKeyId;
|
|
final String apiSecretKey;
|
|
final String tradingBaseUrl;
|
|
final String dataBaseUrl;
|
|
final String dataFeed;
|
|
final bool allowLive;
|
|
|
|
bool get hasCredentials =>
|
|
apiKeyId.isNotEmpty && apiSecretKey.isNotEmpty;
|
|
|
|
bool get isPaperUrl =>
|
|
tradingBaseUrl.contains('paper-api') ||
|
|
!tradingBaseUrl.contains(liveTradingHost);
|
|
|
|
factory AlpacaEnv.fromMap(Map<String, String> env) {
|
|
return AlpacaEnv(
|
|
apiKeyId: env['ALPACA_API_KEY_ID'] ?? '',
|
|
apiSecretKey: env['ALPACA_API_SECRET_KEY'] ?? '',
|
|
tradingBaseUrl: _normalizeBaseUrl(
|
|
env['ALPACA_TRADING_BASE_URL'] ?? defaultPaperTradingUrl,
|
|
),
|
|
dataBaseUrl: _normalizeBaseUrl(
|
|
env['ALPACA_DATA_BASE_URL'] ?? defaultDataUrl,
|
|
),
|
|
dataFeed: env['ALPACA_DATA_FEED'] ?? 'iex',
|
|
allowLive: (env['ALPACA_ALLOW_LIVE'] ?? 'false').toLowerCase() == 'true',
|
|
);
|
|
}
|
|
|
|
/// Strips a trailing `/v2` (or `/v2/`) and trailing slashes from a base URL
|
|
/// so the clients can append `/v2/...` without producing `/v2/v2/...`.
|
|
static String _normalizeBaseUrl(String raw) {
|
|
String url = raw.trim();
|
|
while (url.endsWith('/')) {
|
|
url = url.substring(0, url.length - 1);
|
|
}
|
|
if (url.endsWith('/v2')) {
|
|
url = url.substring(0, url.length - 3);
|
|
}
|
|
return url;
|
|
}
|
|
|
|
/// Refuses live trading host unless [allowLive] is true.
|
|
void assertPaperOnly() {
|
|
final Uri uri = Uri.parse(tradingBaseUrl);
|
|
final bool isLiveHost = uri.host == liveTradingHost;
|
|
if (isLiveHost && !allowLive) {
|
|
throw StateError(
|
|
'Live Alpaca trading URL is not allowed when ALPACA_ALLOW_LIVE=false',
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Requires non-empty API credentials before outbound Alpaca calls.
|
|
void requireCredentials() {
|
|
if (!hasCredentials) {
|
|
throw StateError(
|
|
'ALPACA_API_KEY_ID and ALPACA_API_SECRET_KEY are required',
|
|
);
|
|
}
|
|
}
|
|
}
|